﻿using System.Windows;
using System.Windows.Input;
using System.IO;
using System.Collections.Generic;
using Microsoft.Win32;
using ICSharpCode.AvalonEdit.Highlighting;
using System.Text;
using System;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Documents;
using ICSharpCode.AvalonEdit.Utils;
using System.Windows.Media.Imaging;
using System.Xml;
using System.Windows.Markup;
using MahApps.Metro.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using ICSharpCode.AvalonEdit.Document;
using System.Media;
using System.Text.RegularExpressions;
using System.Windows.Shell;
using mshtml;
using static LunarSF.SHomeWorkshop.LunarMarkdownEditor.FindLineTreeViewItem;
using LunarSF.SHomeWorkshop.LunarMarkdownEditor.CustomWebBrowser;
using System.ComponentModel;
using System.Windows.Navigation;

namespace LunarSF.SHomeWorkshop.LunarMarkdownEditor
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : MetroWindow
    {
        /// <summary>
        /// Html Help Workshop安装路径。
        /// </summary>
        private string hhwInstalledPath = Globals.hhwInstalledPath;
        /// <summary>
        /// Html Help Workshop安装路径。
        /// </summary>
        public string HHWInstalledPath { get { return this.hhwInstalledPath; } }

        /// <summary>
        /// Html Help Compiler(hhc.exe)安装路径。
        /// </summary>
        private string hhcInstalledPath = Globals.hhcInstalledPath;
        /// <summary>
        /// Html Help Compiler(hhc.exe)安装路径。
        /// </summary>
        public string HHCInstalledPath { get { return this.hhcInstalledPath; } }

        private ImageSource previewAppImageSource;
        /// <summary>
        /// 应用程序图标的放大版，用于默认情况下图像预览区预览图像。
        /// </summary>
        public ImageSource PreviewAppImageSource
        {
            get { return previewAppImageSource; }
        }

        /// <summary>
        /// [构造方法]，初始化主窗口。
        /// </summary>
        public MainWindow()
        {
            InitializeComponent();

            previewAppImageSource = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/App.png"));

            tcManagerPanels.IsItemListButtonVisible = false;
            tcRightToolBar.IsItemListButtonVisible = false;

            #region 用来解决主选项卡右上角按钮弹出各文件列表功能
            //mainTabControl.ItemsListBox.BorderThickness = new Thickness(1, 0, 1, 0);
            //mainTabControl.ItemsListBox.BorderBrush = Brushes.DarkGoldenrod;
            mainTabControl.IsItemListButtonVisible = true;
            mainTabControl.ItemListButtonClicked += MainTabControl_ItemListButtonClicked;
            #endregion

            try
            {
                this.workspaceManager = new WorkspaceManager(this);

                //须移动到Loaded事件末尾，否则在工作区管理器中的条目尚未载入的情况下易出错。
                //this.mainTabControl.SelectionChanged += mainTabControl_SelectionChanged;
                this.Closing += MainWindow_Closing;
                this.Loaded += MainWindow_Loaded;

                //提供托盘图标
                CreateNoticeIcon();

                //保证窗体显示在上方。
                windowState = WindowState;

                helpFrame.Source = new Uri(Globals.PathOfWorkspace + "Help~.html");//不能加这个前缀"file:///" +

                #region 载入之前指定的各个历史工作区目录
                if (File.Exists(Globals.PathOfHistoryWorkspaceFileFullName))
                {
                    string[] workspaces = File.ReadAllLines(Globals.PathOfHistoryWorkspaceFileFullName);
                    if (workspaces.Length > 0)
                    {
                        for (int i = 0; i < workspaces.Length; i++)
                        {
                            if (string.IsNullOrEmpty(workspaces[i]) == false)
                            {
                                var rw = new RecentDirectoryListBoxItem(workspaces[i])
                                {
                                    ToolTip = "双击设置为当前工作区目录",
                                };
                                rw.MouseDoubleClick += rw_MouseDoubleClick;
                                lbxHistoryWorkspaces.Items.Add(rw);
                            }
                        }
                    }
                }

                RefreshWorkspaceHistoryList();
                #endregion

                #region 载入之前指定的各个历史导出目录
                if (File.Exists(Globals.PathOfHistoryOutputFileFullName))
                {
                    string[] outports = File.ReadAllLines(Globals.PathOfHistoryOutputFileFullName);
                    if (outports.Length > 0)
                    {
                        for (int i = 0; i < outports.Length; i++)
                        {
                            if (string.IsNullOrEmpty(outports[i]) == false)
                            {
                                var row = new RecentDirectoryListBoxItem(outports[i])
                                {
                                    ToolTip = "双击将编译的所有 Html 文件及资源文件导出到该目录",
                                };
                                row.MouseDoubleClick += row_MouseDoubleClick;
                                lbxHistoryOutport.Items.Add(row);
                            }
                        }
                    }
                }

                RefreshOutputHistoryList();
                #endregion

                #region 快捷键事件处理

                //注意，某些快捷键是全局的，因此可以放在主窗口事件中处理；
                //例如增加字号、减小字号、保存文件、全部保存、新建文件、打开文件、关闭窗口（不需要处理）、生成HTML、生成填空文本、帮助文档……
                //而另一些快捷键必须放到TabControl的事件中处理。
                //这些命令使用的快捷键与Explorer使用的快捷键会产生冲突，此时必须在TabControl中实现。
                //包括：Ctrl+X,Ctrl+C,Ctrl+V,Ctrl+A,Ctrl+Z,Ctrl+Y,Del

                //*** 不清楚是不是会产生冲突的快捷键，为防止误操作，一律在主窗口的键盘事件中进行处理

                this.PreviewKeyDown += MainWindow_PreviewKeyDown;
                mainTabControl.PreviewKeyDown += mainTabControl_PreviewKeyDown;

                #endregion

                //treeview items 变色
                //已无必要存在，因为使用了 MetroTreeViewItem 样式后即使失焦也可以保留当前选择的项。
                //tvWorkDirectory.SelectedItemChanged += TvWorkDirectory_SelectedItemChanged;
                //tvFindAndReplace.SelectedItemChanged += TvFind_SelectedItemChanged;
                //tvTaskList.SelectedItemChanged += TvFind_SelectedItemChanged;

                //载入用户偏好设置项目
                LoadUserPreference();

                // 刷新查找、替换按钮的可用状态
                RefreshFindButtonsStatus();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message + "\r\n" + ex.StackTrace);
            }

            WorkspaceChanged += MainWindow_WorkspaceChanged;
        }

        private WebSearchWindow searchWindow = null;
        /// <summary>
        /// 搜索框。
        /// </summary>
        public WebSearchWindow SearchWindow
        {
            get
            {
                if (searchWindow == null)
                {
                    searchWindow = new WebSearchWindow() { Owner = this, };
                }
                return searchWindow;
            }
        }

        private void MainWindow_WorkspaceChanged(object sender, WorkspaceChangedEventArgs e)
        {
            var title = GetMetaFileTitleOfDirectory(e.NewWorkspaceDirectoryFullPath);
            if (string.IsNullOrWhiteSpace(title))
            {
                DirectoryInfo di = new DirectoryInfo(e.NewWorkspaceDirectoryFullPath);
                if (di != null)
                {
                    this.Title = di.Name + $" - {Globals.AppName}";
                }
                else this.Title = Globals.AppName;
            }
            else
            {
                this.Title = title + $" - {Globals.AppName}";
            }

            this.notifyIcon.Text = this.Title;

        }

        /// <summary>
        /// 请理任务栏上的JumpList
        /// </summary>
        private void ClearJumpList()
        {
            JumpList jumpList1 = JumpList.GetJumpList(App.Current);
            jumpList1.JumpItems.Clear();
            jumpList1.Apply();
        }

        private void MainTabControl_ItemListButtonClicked(object sender, MouseButtonEventArgs e)
        {
            if (mainTabControl.ItemsScrollViewer.Visibility != Visibility.Collapsed)
            {
                mainTabControl.ItemsScrollViewer.Visibility = Visibility.Collapsed;
                return;
            }

            //VirtualizingStackPanel.SetIsVirtualizing(this.mainTabControl.ItemsListBox, true);
            //VirtualizingStackPanel.SetVirtualizationMode(this.mainTabControl.ItemsListBox, VirtualizationMode.Recycling);
            //ScrollViewer.SetCanContentScroll(this.mainTabControl.ItemsListBox, false);

            mainTabControl.ItemsStackPanel.Style = null;
            mainTabControl.ItemsScrollViewer.Background = Brushes.White;
            mainTabControl.ItemsScrollViewer.Width = mainTabControl.ActualWidth / 2;
            mainTabControl.ItemsScrollViewer.MaxWidth = 340;
            mainTabControl.ItemsScrollViewer.Visibility = Visibility.Visible;

            mainTabControl.ItemsStackPanel.Children.Clear();

            var squareButtonStyle = FindResource("SquareButtonStyle") as Style;

            foreach (var item in mainTabControl.Items)
            {
                if (item == mainTabControl.SelectedItem) continue;
                var mde = item as MarkdownEditor;
                if (mde == null) continue;

                //StackPanel sp = new StackPanel() { Orientation = Orientation.Vertical, };
                StackPanel spHeader = new StackPanel() { Orientation = Orientation.Horizontal, };
                TextBlock tbHeader = new TextBlock()
                {
                    Text = mde.Title,
                    Margin = new Thickness(4, 2, 4, 2),
                    Width = 320,
                };
                //Image imgheader = new Image() { Width = 16, Height = 16, };
                //imgheader.Source = mde.IconSource;
                //spHeader.Children.Add(imgheader);
                spHeader.Children.Add(tbHeader);

                //TextBlock tbComment = new TextBlock()
                //{
                //    Text = ":> " + mde.FullFilePath,
                //    Margin = new Thickness(4, 2, 4, 2),
                //    Width = 320,
                //    Foreground = Brushes.DarkGoldenrod,
                //    TextWrapping = TextWrapping.WrapWithOverflow,
                //};
                //sp.Children.Add(spHeader);
                //sp.Children.Add(tbComment);

                //var border = new Border()
                //{
                //    Child = sp,
                //    BorderBrush = Brushes.DarkGoldenrod,
                //    BorderThickness = new Thickness(0, 0, 0, 1),
                //    SnapsToDevicePixels = true,
                //};

                var li = new Button()
                {
                    Style = squareButtonStyle,
                    FontWeight = FontWeights.Normal,
                    FontSize = 14,
                    BorderThickness = new Thickness(0, 0, 0, 1),
                    Tag = mde,
                    Content = spHeader,
                    Margin = new Thickness(0, 4, 0, 4),
                    ToolTip = "点击切换到对应文档",
                };
                li.Click += Li_Click;

                mainTabControl.ItemsStackPanel.Children.Add(li);
            }
        }

        private void Li_Click(object sender, RoutedEventArgs e)
        {
            mainTabControl.ItemsStackPanel.Children.Clear();
            mainTabControl.ItemsScrollViewer.Visibility = Visibility.Collapsed;

            mainTabControl.SelectedItem = (sender as Button).Tag as MarkdownEditor;
        }

        //private void TvFind_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        //{
        //    //已无必要存在，因为使用了 MetroTreeViewItem 样式后即使失焦也可以保留当前选择的项。
        //    //IFindItem oldItem = (e.OldValue as IFindItem);
        //    //if (oldItem != null)
        //    //{
        //    //    oldItem.HeaderTextBlock.Foreground = oldItem.ForegroundOfText;
        //    //    oldItem.BorderBrush = Brushes.Transparent;
        //    //}

        //    //var newItem = (e.NewValue as IFindItem);
        //    //if (newItem != null)
        //    //{
        //    //    newItem.HeaderTextBlock.Foreground = Brushes.Black;
        //    //    newItem.BorderBrush = WorkspaceTreeViewItem.TreeViewItemBorderBrush;
        //    //}
        //}

        /// <summary>
        /// 工作区管理器中各条目变色功能。
        /// </summary>
        //private void TvWorkDirectory_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        //{
        //    //已无必要存在，因为使用了 MetroTreeViewItem 样式后即使失焦也可以保留当前选择的项。
        //    //var oldItem = (e.OldValue as WorkspaceTreeViewItem);
        //    //if (oldItem != null)
        //    //{
        //    //    oldItem.HeaderTextBlock.Foreground = oldItem.ForegroundOfText;
        //    //    oldItem.BorderBrush = Brushes.Transparent;
        //    //}

        //    //var newItem = (e.NewValue as WorkspaceTreeViewItem);
        //    //if (newItem != null)
        //    //{
        //    //    newItem.HeaderTextBlock.Foreground = Brushes.Black;
        //    //    newItem.BorderBrush = WorkspaceTreeViewItem.TreeViewItemBorderBrush;
        //    //}
        //}

        #region 字体相关

        private FontFamily defaultFontFamily;

        public FontFamily DefaultFontFamily
        {
            get { return defaultFontFamily; }
            set { defaultFontFamily = value; }
        }

        private bool _familyListValid;
        private ICollection<FontFamily> _familyCollection;

        public ICollection<FontFamily> FontFamilyCollection
        {
            get
            {
                return (_familyCollection == null) ? Fonts.SystemFontFamilies : _familyCollection;
            }

            set
            {
                if (value != _familyCollection)
                {
                    _familyCollection = value;
                    InvalidateFontFamilyList();
                }
            }
        }

        private void InvalidateFontFamilyList()
        {
            if (_familyListValid)
            {
                fontFamilyList.Items.Clear();
                _familyListValid = false;
            }
        }

        private void InitializeFontFamilyList()
        {
            ICollection<FontFamily> familyCollection = FontFamilyCollection;
            if (familyCollection != null)
            {
                FontFamilyListItem[] items = new FontFamilyListItem[familyCollection.Count];

                int i = 0;

                foreach (FontFamily family in familyCollection)
                {
                    items[i++] = new FontFamilyListItem(family);
                }

                Array.Sort<FontFamilyListItem>(items);

                foreach (FontFamilyListItem item in items)
                {
                    fontFamilyList.Items.Add(new ComboBoxItem() { Content = item, });
                }
            }
        }

        #endregion

        /// <summary>
        /// 双击“最近工作区列表”中的某个条目，将工作区切换到该条目记录的目录。
        /// </summary>
        void rw_MouseDoubleClick(object sender, MouseButtonEventArgs e)
        {
            ChangeWorkspace((sender as RecentDirectoryListBoxItem).DirectoryPath);
        }

        /// <summary>
        /// 复制特定图像资源文件到指定目录中去。
        /// </summary>
        /// <param name="destImageFolder">目标位置。</param>
        /// <param name="fileShortName">图像文件短名。</param>
        private static void CopyImageFile(string destImageFolder, string fileShortName)
        {
            var destIamgeFilePath = destImageFolder + fileShortName;
            var srcImageFilePath = Globals.DefaultWorkspacePath + $"Images~\\{fileShortName}";
            if (File.Exists(srcImageFilePath))
            {
                if (File.Exists(destIamgeFilePath))
                {
                    FileInfo srcIamgeFileInfo = new FileInfo(srcImageFilePath);
                    FileInfo destImageFileInfo = new FileInfo(destIamgeFilePath);
                    if (destImageFileInfo.LastWriteTime.CompareTo(srcIamgeFileInfo.LastWriteTime) < 0)
                    {
                        File.Copy(srcImageFilePath, destIamgeFilePath, true);
                    }
                }
                else
                {
                    File.Copy(srcImageFilePath, destIamgeFilePath, true);
                }
            }
        }

        /// <summary>
        /// 将编译后的 Html 文件需要的资源文件复制到目标工作区目录中去。
        /// </summary>
        /// <param name="workspace">目标工作区目录路径。</param>
        private static void CopyCssAndResourceFiles(string workspace)
        {
            if (string.IsNullOrEmpty(workspace)) return;

            if (workspace.EndsWith("\\") == false) workspace += "\\";

            if (workspace == Globals.DefaultWorkspacePath) return;//不需要复制。

            if (Directory.Exists(workspace) == false)
            {
                Directory.CreateDirectory(workspace);
            }

            var destImageFolder = workspace + "Images~\\";
            if (Directory.Exists(destImageFolder) == false)
            {
                Directory.CreateDirectory(destImageFolder);
            }

            try
            {
                CopyImageFile(destImageFolder, "header_icon_dark.png");
                CopyImageFile(destImageFolder, "header_icon_light.png");
                CopyImageFile(destImageFolder, "comment_dark.png");
                CopyImageFile(destImageFolder, "comment_light.png");

                CopyImageFile(destImageFolder, "region_e_dark.png");
                CopyImageFile(destImageFolder, "region_e_light.png");
                CopyImageFile(destImageFolder, "region_i_dark.png");
                CopyImageFile(destImageFolder, "region_i_light.png");
                CopyImageFile(destImageFolder, "region_q_dark.png");
                CopyImageFile(destImageFolder, "region_q_light.png");
                CopyImageFile(destImageFolder, "region_w_dark.png");
                CopyImageFile(destImageFolder, "region_w_light.png");

                CopyImageFile(destImageFolder, "menu_light.png");
                CopyImageFile(destImageFolder, "menu_dark.png");

                //jQuery 2.0以上版本对IE的版本有要求，不适合本程序使用。
                var destJQuery = workspace + "jquery-1.7.0.min.js";
                if (File.Exists(destJQuery) == false)
                {
                    File.Copy(Globals.DefaultWorkspacePath + "jquery-1.7.0.min.js", destJQuery);
                }

                var destLessonDarkCSS = workspace + "lesson_dark.css";
                var srcLessonDarkCSS = Globals.DefaultWorkspacePath + "lesson_dark.css";
                if (File.Exists(srcLessonDarkCSS))
                {
                    if (File.Exists(destLessonDarkCSS))
                    {
                        FileInfo srcLessonDarkCSSFileInfo = new FileInfo(srcLessonDarkCSS);
                        FileInfo destLessonDarkCSSFileInfo = new FileInfo(destLessonDarkCSS);
                        if (destLessonDarkCSSFileInfo.LastWriteTime.CompareTo(srcLessonDarkCSSFileInfo.LastWriteTime) < 0)
                        {
                            File.Copy(srcLessonDarkCSS, destLessonDarkCSS, true);
                        }
                    }
                    else
                    {
                        File.Copy(srcLessonDarkCSS, destLessonDarkCSS, true);
                    }
                }

                var destLessonLightCSS = workspace + "lesson_light.css";
                var srcLessonLightCSS = Globals.DefaultWorkspacePath + "lesson_light.css";
                if (File.Exists(srcLessonLightCSS))
                {
                    if (File.Exists(destLessonLightCSS))
                    {
                        FileInfo destLessonLightCSSFileInfo = new FileInfo(destLessonLightCSS);
                        FileInfo srcLessonLightCSSFileInfo = new FileInfo(srcLessonLightCSS);
                        if (destLessonLightCSSFileInfo.LastWriteTime.CompareTo(srcLessonLightCSSFileInfo.LastWriteTime) < 0)
                        {
                            File.Copy(srcLessonLightCSS, destLessonLightCSS, true);
                        }
                    }
                    else
                    {
                        File.Copy(srcLessonLightCSS, destLessonLightCSS, true);
                    }
                }

                var destMenuLightCSS = workspace + "menu_light.css";
                var srcMenuLightCSS = Globals.DefaultWorkspacePath + "menu_light.css";
                if (File.Exists(srcMenuLightCSS))
                {
                    if (File.Exists(destMenuLightCSS))
                    {
                        FileInfo destMenuLightCSSFileInfo = new FileInfo(destMenuLightCSS);
                        FileInfo srcMenuLightCSSFileInfo = new FileInfo(srcMenuLightCSS);
                        if (destMenuLightCSSFileInfo.LastWriteTime.CompareTo(srcMenuLightCSSFileInfo.LastWriteTime) < 0)
                        {
                            File.Copy(srcMenuLightCSS, destMenuLightCSS, true);
                        }
                    }
                    else
                    {
                        File.Copy(srcMenuLightCSS, destMenuLightCSS, true);
                    }
                }

                var destMenuDarkCSS = workspace + "menu_dark.css";
                var srcMenuDarkCSS = Globals.DefaultWorkspacePath + "menu_dark.css";
                if (File.Exists(srcMenuDarkCSS))
                {
                    if (File.Exists(destMenuDarkCSS))
                    {
                        FileInfo destMenuDarkCSSFileInfo = new FileInfo(destMenuDarkCSS);
                        FileInfo srcMenuDarkCSSFileInfo = new FileInfo(srcMenuDarkCSS);
                        if (destMenuDarkCSSFileInfo.LastWriteTime.CompareTo(srcMenuDarkCSSFileInfo.LastWriteTime) < 0)
                        {
                            File.Copy(srcMenuDarkCSS, destMenuDarkCSS, true);
                        }
                    }
                    else
                    {
                        File.Copy(srcMenuDarkCSS, destMenuDarkCSS, true);
                    }
                }

                var destMenuLightJS = workspace + "menu_light.js";
                var srcMenuLightJS = Globals.DefaultWorkspacePath + "menu_light.js";
                if (File.Exists(srcMenuLightJS))
                {
                    if (File.Exists(destMenuLightJS))
                    {
                        FileInfo destMenuLightJSFileInfo = new FileInfo(destMenuLightJS);
                        FileInfo srcMenuLightJSFileInfo = new FileInfo(srcMenuLightJS);
                        if (destMenuLightJSFileInfo.LastWriteTime.CompareTo(srcMenuLightJSFileInfo.LastWriteTime) < 0)
                        {
                            File.Copy(srcMenuLightJS, destMenuLightJS, true);
                        }
                    }
                    else
                    {
                        File.Copy(srcMenuLightJS, destMenuLightJS, true);
                    }
                }

                var destMenuDarkJS = workspace + "menu_dark.js";
                var srcMenuDarkJS = Globals.DefaultWorkspacePath + "menu_dark.js";
                if (File.Exists(srcMenuDarkJS))
                {
                    if (File.Exists(destMenuDarkJS))
                    {
                        FileInfo destMenuDarkJSFileInfo = new FileInfo(destMenuDarkJS);
                        FileInfo srcMenuDarkJSFileInfo = new FileInfo(srcMenuDarkJS);
                        if (destMenuDarkJSFileInfo.LastWriteTime.CompareTo(srcMenuDarkJSFileInfo.LastWriteTime) < 0)
                        {
                            File.Copy(srcMenuDarkJS, destMenuDarkJS, true);
                        }
                    }
                    else
                    {
                        File.Copy(srcMenuDarkJS, destMenuDarkJS, true);
                    }
                }

                var destPresentationDarkCSS = workspace + "presentation_dark.css";
                var srcPresentationDarkCSS = Globals.DefaultWorkspacePath + "presentation_dark.css";
                if (File.Exists(srcPresentationDarkCSS))
                {
                    if (File.Exists(destPresentationDarkCSS))
                    {
                        FileInfo srcPresentationDarkCSSFileInfo = new FileInfo(srcPresentationDarkCSS);
                        FileInfo destPresentationDarkCSSFileInfo = new FileInfo(destPresentationDarkCSS);
                        if (destPresentationDarkCSSFileInfo.LastWriteTime.CompareTo(srcPresentationDarkCSSFileInfo.LastWriteTime) < 0)
                        {
                            File.Copy(srcPresentationDarkCSS, destPresentationDarkCSS, true);
                        }
                    }
                    else
                    {
                        File.Copy(srcPresentationDarkCSS, destPresentationDarkCSS, true);
                    }
                }

                var destPresentationLightCSS = workspace + "presentation_light.css";
                var srcPresentationLightCSS = Globals.DefaultWorkspacePath + "presentation_light.css";
                if (File.Exists(srcPresentationLightCSS))
                {
                    if (File.Exists(destPresentationLightCSS))
                    {
                        FileInfo destPresentationLightCSSFileInfo = new FileInfo(destPresentationLightCSS);
                        FileInfo srcPresentationLightCSSFileInfo = new FileInfo(srcPresentationLightCSS);
                        if (destPresentationLightCSSFileInfo.LastWriteTime.CompareTo(srcPresentationLightCSSFileInfo.LastWriteTime) < 0)
                        {
                            File.Copy(srcPresentationLightCSS, destPresentationLightCSS, true);
                        }
                    }
                    else
                    {
                        File.Copy(srcPresentationLightCSS, destPresentationLightCSS, true);
                    }
                }

                var destHelpHtml = workspace + "Help~.html";
                var srcHelpHtml = Globals.DefaultWorkspacePath + "Help~.html";
                if (File.Exists(srcHelpHtml))
                {
                    if (File.Exists(destHelpHtml))
                    {
                        FileInfo srcHelpHtmlFileInfo = new FileInfo(srcHelpHtml);
                        FileInfo destHelpHtmlFileInfo = new FileInfo(destHelpHtml);
                        if (destHelpHtmlFileInfo.LastWriteTime.CompareTo(srcHelpHtmlFileInfo.LastWriteTime) < 0)
                        {
                            File.Copy(srcHelpHtml, destHelpHtml, true);
                        }
                    }
                    else
                    {
                        File.Copy(srcHelpHtml, destHelpHtml, true);
                    }
                }
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace);
            }
        }

        public void LoadDefaultWorkspace(string path = null)
        {
            App.WorkspaceConfigManager = null;//刷新配置管理器。

            //载入工作区
            var destWorkspaceFolder = LoadWorkspaceDirectory(path);

            //刷新工作区管理器
            if (workspaceManager.LoadWorkspaceFromXml(null) == false)
            {
                WorkspaceManager.RefreshWorkspaceTreeView(null);         //已限制
            }

            //载入工作区 Html 编译选项。
            LoadWorkspaceHtmlCompileOptions();

            // [废弃]刷新工作区管理器中各项目的折叠状态。
            // LoadWorkspaceCollapseStatus();

            LoadRememberOpenedFiles();//会自动判断要不要载入。

            WriteHistoryWorkspacesFile(destWorkspaceFolder);

            RefreshWorkspaceHistoryList();

            //tvWorkDirectory.IsEnabled = true;

            if (this.PerspectiveMode == Perspective.CompareMode)
            {
                var activeEditor = ActivedEditor;
                if (activeEditor != null)
                {
                    activeEditor.SendToRightCompareArea();
                }
            }
        }

        /// <summary>
        /// 打开指定的工作区目录。
        /// </summary>
        /// <param name="path">如指定此参数，优先打开此参数指定的目录。</param>
        /// <returns></returns>
        private string LoadWorkspaceDirectory(string path = null)
        {
            //载入指定的工作区目录
            string workspace;
            if (Directory.Exists(path))
            {
                workspace = path;
            }
            else workspace = App.ConfigManager.Get("Workspace");

            if (Directory.Exists(workspace))
            {
                if (workspace.EndsWith("\\") == false) workspace += "\\";
                if (Directory.Exists(workspace))
                {
                    Globals.PathOfWorkspace = workspace;
                }

                //用于判断当前工作区目录是否已被打开，以便提示用户不要重复打开工作区
                File.WriteAllText(Globals.PathOfWorkspace + "~.editing", "This workspace is in editing.");
            }

            //检查工作区目录是否存在。
            if (Directory.Exists(Globals.PathOfWorkspace) == false)
            {
                try
                {
                    Directory.CreateDirectory(Globals.PathOfWorkspace);

                    //检查必须的文件是否存在。
                    CopyCssAndResourceFiles(Globals.PathOfWorkspace);
                }
                catch (System.Exception ex)
                {
                    LMessageBox.Show("　　未能在创建工作区目录。这可能是因为Windows系统权限造成的。一般来说，不应将工作区指定在系统目录下。异常消息如下：\r\n" +
                        ex.Message + "\r\n" + ex.StackTrace,
                        Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                }
            }
            else
            {
                if (Globals.PathOfWorkspace.ToLower() != Globals.DefaultWorkspacePath.ToLower())
                {
                    //检查必须的文件是否存在。
                    CopyCssAndResourceFiles(Globals.PathOfWorkspace);
                }
            }

            tbxPathOfWorkspace.Text = Globals.PathOfWorkspace;
            AddJumpTask(Globals.PathOfWorkspace);

            var eventArgs = new WorkspaceChangedEventArgs()
            {
                OldWorkspaceDirectoryFullPath = Globals.PathOfWorkspace,
                NewWorkspaceDirectoryFullPath = Globals.PathOfWorkspace,
            };
            OnWorkspaceChanged(this, eventArgs);

            return Globals.PathOfWorkspace;
        }

        private void AddJumpTask(string workspacePath)
        {
            if (Directory.Exists(workspacePath) == false) return;
            if (workspacePath[workspacePath.Length - 1] != Path.DirectorySeparatorChar)
                workspacePath += Path.DirectorySeparatorChar;

            DirectoryInfo diWorkspace = new DirectoryInfo(workspacePath);

            #region 在任务栏JumpList中添加对应项。
            JumpTask jumpTask1 = new JumpTask();
            // Get the path to Calculator and set the JumpTask properties.
            jumpTask1.ApplicationPath = Globals.AppFullPath;
            jumpTask1.WorkingDirectory = Globals.InstalledPath;
            jumpTask1.IconResourcePath = Globals.AppFullPath;
            jumpTask1.Title = diWorkspace.Name;
            jumpTask1.Description = diWorkspace.FullName;
            jumpTask1.Arguments = diWorkspace.FullName.Contains(" ") ? ("\"" + diWorkspace.FullName + "\"") : diWorkspace.FullName;
            var jumpList = JumpList.GetJumpList(App.Current);

            var b = workspacePath.ToLower();
            if (b.EndsWith("\\") == false) b += "\\";

            for (int i = jumpList.JumpItems.Count - 1; i >= 0; i--)
            {
                JumpTask item = jumpList.JumpItems[i] as JumpTask;
                if (item == null) continue;

                var a = item.Arguments.ToLower();
                if (a.EndsWith("\\") == false) a += "\\";

                if (a == b)
                {
                    jumpList.JumpItems.RemoveAt(i);
                }
            }

            JumpList.AddToRecentCategory(jumpTask1);
            jumpList.Apply();
            #endregion
        }

        private StartWorkspaceSelector startWorkspaceSelector = null;

        public StartWorkspaceSelector StartWorkspaceSelector
        {
            get { return this.startWorkspaceSelector; }
            set { this.startWorkspaceSelector = value; }
        }

        /// <summary>
        /// 在主窗口载入后进行一些初始化操作。
        /// </summary>
        void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            #region 更改几个组合框下拉箭头的配色
            var tbtnPath1 = FindVisualChildItem<System.Windows.Shapes.Path>(fontFamilyList, "BtnArrow");
            if (tbtnPath1 != null) tbtnPath1.Fill = Brushes.White;

            var tbtnPath2 = FindVisualChildItem<System.Windows.Shapes.Path>(cmbSearchArea2, "BtnArrow");
            if (tbtnPath2 != null) tbtnPath2.Fill = Brushes.White;

            var tbtnPath3 = FindVisualChildItem<System.Windows.Shapes.Path>(cmbPerspective, "BtnArrow");
            if (tbtnPath3 != null) tbtnPath3.Fill = Brushes.White;
            #endregion

            #region 更改 Metro 菜单 PopUp 的显示位置，以保持风格一致
            foreach (var ue in mainMenu.Items)
            {
                var mi = ue as MenuItem;
                if (mi == null) continue;

                var partPopup = FindVisualChildItem<Popup>(mi, "PART_Popup");
                if (partPopup != null)
                {
                    // 这里如果去除，第一次弹出的位置就不准确
                    partPopup.Placement = PlacementMode.Absolute;
                    var point = mi.PointToScreen(new Point(0, 0));
                    partPopup.HorizontalOffset = point.X + 1;
                    partPopup.VerticalOffset = point.Y + mi.ActualHeight - 2;
                }

                mi.MouseEnter += Mi_MouseEnter;
                mi.MouseLeave += Mi_MouseLeave;
                mi.SubmenuOpened += Mi_SubmenuOpened;  // 少了这行，之后弹出的位置可能不准确
                //此方案貌似仍然有些潜在的问题，可惜重定义 MetroContextStyle 模板代价太高。
            }
            #endregion

            try
            {
                //载入前导字符串的正则表达式。
                var manager = App.ConfigManager;
                if (manager != null)
                {
                    leaderTextRegsA = manager.Get("LeaderTextRegsA");
                    leaderTextRegsB = manager.Get("LeaderTextRegsB");
                    ReplaceLeaderChar = manager.Get("ReplaceLeaderChar");  //这里用属性，不用字段
                }


                //载入程序上次关闭前使用的透视图模式
                LoadRememberedPerspectiveMode();
                cmbPerspective.SelectionChanged += cmbPerspective_SelectionChanged;

                StringBuilder sb = new StringBuilder();
                if (Globals.args != null && Globals.args.Length > 0)
                {
                    for (int i = 0; i < Globals.args.Length; i++)
                    {
                        var s = Globals.args[i];
                        sb.Append($"args[{i}]" + s + "\r\n");
                    }
                }

                var cmdParameterPath = Globals.CmdParameterString;

                //MessageBox.Show("1 " + cmdParameterPath);
                //测试用代码。使用项目属性→调试→命令行参数并不能解决全部问题。

                if (cmdParameterPath.StartsWith("\"\"") && cmdParameterPath.EndsWith("\"\""))
                {
                    cmdParameterPath = cmdParameterPath.Substring(1, cmdParameterPath.Length - 2);
                }

                //MessageBox.Show("2 " + cmdParameterPath);
                //测试用代码。使用项目属性→调试→命令行参数并不能解决全部问题。

                if (cmdParameterPath.StartsWith("\"") && cmdParameterPath.EndsWith("\""))
                {
                    cmdParameterPath = cmdParameterPath.Substring(1, cmdParameterPath.Length - 2);
                }

                if (Directory.Exists(cmdParameterPath))
                {
                    LoadDefaultWorkspace(cmdParameterPath);
                    return;
                }

                if (this.SelectWorkspaceAtStart &&
                    File.Exists(Globals.PathOfHistoryWorkspaceFileFullName))
                {
                    string[] workspaces = File.ReadAllLines(Globals.PathOfHistoryWorkspaceFileFullName);
                    if (workspaces.Length > 0)
                    {
                        startWorkspaceSelector = new LunarMarkdownEditor.StartWorkspaceSelector()
                        {
                            Owner = this,
                            WindowStartupLocation = WindowStartupLocation.CenterScreen,
                        };

                        startWorkspaceSelector.Show();
                    }
                    else
                    {
                        LoadDefaultWorkspace();
                    }
                }
                else
                {
                    LoadDefaultWorkspace();
                }

                this.mainTabControl.SelectionChanged += mainTabControl_SelectionChanged;

            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message + "\r\n" + ex.StackTrace);
            }

            this.Activated += MainWindow_Activated;
        }

        private void Mi_SubmenuOpened(object sender, RoutedEventArgs e)
        {
            var mi = sender as MenuItem;
            if (mi == null) return;

            var partPopup = FindVisualChildItem<Popup>(mi, "PART_Popup");
            if (partPopup != null)
            {
                partPopup.Placement = PlacementMode.Absolute;
                var point = mi.PointToScreen(new Point(0, 0));
                partPopup.HorizontalOffset = point.X + 1;
                partPopup.VerticalOffset = point.Y + mi.ActualHeight - 2;
            }
        }

        private void Mi_MouseLeave(object sender, MouseEventArgs e)
        {
            var mi = sender as MenuItem;
            mi.Foreground = Brushes.White;
            mi.Background = Brushes.Transparent;
        }

        private void Mi_MouseEnter(object sender, MouseEventArgs e)
        {
            var mi = sender as MenuItem;
            mi.Foreground = Brushes.Black;
            mi.Background = Brushes.LightGray;
        }

        private void MainWindow_Activated(object sender, EventArgs e)
        {
            if (startWorkspaceSelector != null && startWorkspaceSelector.Visibility == Visibility.Visible)
            {
                startWorkspaceSelector.Activate();
            }
        }

        private void LoadUserPreference()
        {
            //载入程序上次关闭前使用的透视图模式
            //LoadRememberedPerspectiveMode();
            //cmbPerspective.SelectionChanged += cmbPerspective_SelectionChanged;
            //窗口尚未载入，此时无法获取准确的窗口宽度

            //载入字体列表
            InitializeFontFamilyList();

            //载入默认字体
            this.defaultFontFamily = mainTabControl.FontFamily;
            var defFontFamilyEnName = App.ConfigManager.Get("FontFamily");
            foreach (var item in fontFamilyList.Items)
            {
                ComboBoxItem cbi = item as ComboBoxItem;
                if (cbi == null) continue;

                FontFamilyListItem fli = cbi.Content as FontFamilyListItem;
                if (fli == null) continue;

                string enName;
                if (fli.FontFamily.FamilyNames.TryGetValue(System.Windows.Markup.XmlLanguage.GetLanguage("en-us"), out enName))
                {
                    if (defFontFamilyEnName == enName)
                    {
                        this.mainTabControl.FontFamily = fli.FontFamily;
                        this.fontFamilyList.SelectedItem = cbi;
                        this.tvOutLine.FontFamily = fli.FontFamily;
                        break;
                    }
                }
            }

            this.fontFamilyList.SelectionChanged += fontFamilyList_SelectionChanged;

            //载入格式化动作何时执行的设置
            var format = App.ConfigManager.Get("AutoFormat");
            if (string.IsNullOrEmpty(format) == false && (format.ToLower() == "true"))
                miFormatBeforeSave.IsChecked = true;
            else
                miFormatBeforeSave.IsChecked = false;

            //是否禁止双开应用程序
            var unableDoubleInstanceText = App.ConfigManager.Get("UnableDoubleInstance");
            if (string.IsNullOrWhiteSpace(unableDoubleInstanceText) == false)
            {
                bool udi;
                if (bool.TryParse(unableDoubleInstanceText, out udi))
                {
                    App.UnableDoubleInstance = udi;
                }
            }
            miUnableDoubleInstance.IsChecked = App.UnableDoubleInstance;

            //启动时不显示工作区选择器（而是直接打开上次记录的工作区）
            var selectWorkspaceAtStart = App.ConfigManager.Get("SelectWorkspaceAtStart");
            if (string.IsNullOrEmpty(selectWorkspaceAtStart) == false)
            {
                bool dsws;
                if (bool.TryParse(selectWorkspaceAtStart, out dsws))
                {
                    this.SelectWorkspaceAtStart = dsws;
                }
            }
            miSelectWorkspaceAtStart.IsChecked = this.SelectWorkspaceAtStart;

            helpFrame.Source = new Uri(Globals.DefaultWorkspacePath + "Help~.html");//不能加此前缀"file:///" + 

            //读取Html Help Workshop.exe的路径。
            if (File.Exists(Globals.hhwInstalledPath) == false)
            {
                //为什么要这样做呢？
                //这是因为版权问题，不便集成Html Help Workshop到我的安装包中。
                //但问题在于，从网上下载的各个版本（哪怕是微软官方网站下载的英文版），在独立状态下都很容易出错。
                //所以还是集成了。这样，原先分离的做法就不想作用了。
                //如果将来还可以分离，这段代码就又有用了。所以未删除。
                var hhwInstalledPath = App.ConfigManager.Get("HHWInstalledPath");
                if (File.Exists(hhwInstalledPath))
                {
                    this.hhwInstalledPath = hhwInstalledPath;
                    var directoryPath = Path.GetDirectoryName(hhwInstalledPath);
                    if (directoryPath.EndsWith("\\") == false)
                        directoryPath += "\\";
                    this.hhcInstalledPath = directoryPath + "hhc.exe";
                }
                else
                {
                    //尝试从注册表中读取安装的hhw.exe的路径
                    RegistryKey regSubKey;
                    RegistryKey regKey = Registry.LocalMachine;
                    string strRegPath = @"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\hhw.exe";
                    regSubKey = regKey.OpenSubKey(strRegPath);
                    if (regSubKey != null)
                    {
                        string pathValue = regSubKey.GetValue("").ToString();
                        if (File.Exists(pathValue))
                        {
                            this.hhwInstalledPath = pathValue;
                            var directoryPath = Path.GetDirectoryName(pathValue);
                            if (directoryPath.EndsWith("\\") == false)
                                directoryPath += "\\";
                            this.hhcInstalledPath = directoryPath + "hhc.exe";
                        }
                    }
                }
            }

            //是否开启自动完成
            var isAutoCompletionEnabledText = App.ConfigManager.Get("IsAutoCompletionEnabled");
            if (string.IsNullOrEmpty(isAutoCompletionEnabledText) == false)
            {
                bool ac;
                if (bool.TryParse(isAutoCompletionEnabledText, out ac))
                {
                    this.IsAutoCompletionEnabled = ac;
                }
            }
            else
            {
                this.IsAutoCompletionEnabled = false;//默认关闭，这玩意儿还是很麻烦的。
            }
            miIsAutoCompletionEnabled.IsChecked = this.IsAutoCompletionEnabled;

            //是否开启试题编辑功能
            var isExamEnabledText = App.ConfigManager.Get("IsExamEnabled");
            if (string.IsNullOrEmpty(isExamEnabledText) == false)
            {
                bool r;
                if (bool.TryParse(isExamEnabledText, out r))
                {
                    this.IsExamEnabled = r;
                }
            }
            else
            {
                this.IsExamEnabled = false;//默认关闭
            }
            miIsExamEnabled.IsChecked = this.IsExamEnabled && this.IsAutoCompletionEnabled;

            //是否开启英译中词典功能
            var isEnToChineseDictEnabledText = App.ConfigManager.Get("IsEnToChineseDictEnabled");
            if (string.IsNullOrEmpty(isEnToChineseDictEnabledText) == false)
            {
                bool ecde;
                if (bool.TryParse(isEnToChineseDictEnabledText, out ecde))
                {
                    this.IsEnToChineseDictEnabled = ecde;
                }
            }
            else
            {
                this.isEnToChineseDictEnabled = false;//默认关闭
            }
            miIsEnToChineseDictEnabled.IsChecked = this.IsEnToChineseDictEnabled;

            var isPopupContextToolbarEnabledText = App.ConfigManager.Get("IsPopupContextToolbarEnabled");
            if (string.IsNullOrWhiteSpace(isPopupContextToolbarEnabledText) == false)
            {
                bool ipct;
                if (bool.TryParse(isPopupContextToolbarEnabledText, out ipct))
                {
                    this.isPopupContextToolbarEnabled = ipct;
                }
            }
            RefreshIsPopupContextToolbarEnabled(this.IsPopupContextToolbarEnabled);

            //是否显示空格占位符
            var isShowSpacesText = App.ConfigManager.Get("IsShowSpaces");
            if (string.IsNullOrEmpty(isShowSpacesText) == false)
            {
                bool ss;
                if (bool.TryParse(isShowSpacesText, out ss))
                {
                    this.IsShowSpaces = ss;
                }
            }
            else
            {
                this.isShowSpaces = false;//默认关闭
            }
            miShowSpaces.IsChecked = this.IsShowSpaces;

            //是否显示段落标记
            var isShowEndOfLineText = App.ConfigManager.Get("IsShowEndOfLine");
            if (string.IsNullOrEmpty(isShowEndOfLineText) == false)
            {
                bool sel;
                if (bool.TryParse(isShowEndOfLineText, out sel))
                {
                    this.IsShowEndOfLine = sel;
                }
            }
            else
            {
                this.isShowEndOfLine = false;//默认关闭
            }
            miShowEndOfLine.IsChecked = this.IsShowEndOfLine;

            //是否显示制表符
            var isShowTabsText = App.ConfigManager.Get("IsShowTabs");
            if (string.IsNullOrEmpty(isShowTabsText) == false)
            {
                bool st;
                if (bool.TryParse(isShowTabsText, out st))
                {
                    this.IsShowTabs = st;
                }
            }
            else
            {
                this.isShowTabs = false;//默认关闭
            }
            miShowTabs.IsChecked = this.IsShowTabs;

            //是否显示行号
            var isShowLinesNumbersText = App.ConfigManager.Get("IsShowLinesNumbers");
            if (string.IsNullOrEmpty(isShowLinesNumbersText) == false)
            {
                bool sln;
                if (bool.TryParse(isShowLinesNumbersText, out sln))
                {
                    this.IsShowLineNumbers = sln;
                }
            }
            else
            {
                this.IsShowLineNumbers = true;//默认开启
            }
            miShowLineNumbers.IsChecked = this.IsShowLineNumbers;

            //载入VimKey
            var vimKeyText = App.ConfigManager.Get("VimKey");
            if (string.IsNullOrWhiteSpace(vimKeyText))
            {
                Globals.VimKey = Key.LeftShift;
                tbVimKeyText.Text = "LShift";
            }
            else
            {
                switch (vimKeyText)
                {
                    case "LeftCtrl":
                        {
                            Globals.VimKey = Key.LeftCtrl;
                            tbVimKeyText.Text = "LCtrl";
                            break;
                        }
                    case "RightCtrl":
                        {
                            Globals.VimKey = Key.RightCtrl;
                            tbVimKeyText.Text = "RCtrl";
                            break;
                        }
                    case "RightShift":
                        {
                            Globals.VimKey = Key.RightShift;
                            tbVimKeyText.Text = "RShift";
                            break;
                        }
                    case "LeftShift":
                    default:
                        {
                            Globals.VimKey = Key.LeftShift;
                            tbVimKeyText.Text = "LShift";
                            break;
                        }
                }
            }

            //在工作区管理器中是否尝试显示标题而非文件短名
            var showTitleInWorkspaceManagerText = App.ConfigManager.Get("ShowTitleInWorkspaceManager");
            if (string.IsNullOrWhiteSpace(showTitleInWorkspaceManagerText) == false)
            {
                bool stiwm;
                if (bool.TryParse(showTitleInWorkspaceManagerText, out stiwm))
                {
                    this.ShowTitleInWorkspaceManager = stiwm;
                }
            }
            else
            {
                this.showTitleInWorkspaceManager = false;//默认关闭
            }
            ckxShowTitle.IsChecked =
            miShowTitleInWorkspaceManager.IsChecked = this.ShowTitleInWorkspaceManager;

            //在文字表区域按 Tab 键是否先选中所在单元格中的所有文本
            var selectCellFirst = App.ConfigManager.Get("SelectCellFirst");
            if (string.IsNullOrEmpty(selectCellFirst) == false)
            {
                bool se;
                if (bool.TryParse(selectCellFirst, out se))
                {
                    this.SelectCellFirst = se;
                }
            }
            miSelectCellFirst.IsChecked = this.SelectCellFirst;

            //TextAutoWrap，文本自动折行
            var textAutoWrap = App.ConfigManager.Get("TextAutoWrap");
            if (string.IsNullOrEmpty(textAutoWrap) == false)
            {
                bool txtAutoWrap;
                if (bool.TryParse(textAutoWrap, out txtAutoWrap))
                {
                    this.textAutoWrap = txtAutoWrap;//注意，不能使用属性，会引起无限递归
                }
            }
            miTextAutoWrap.IsChecked = this.TextAutoWrap;

            //是否点击“关闭”按钮时最小化到托盘图标而不是关闭程序
            var isCloseToIconText = App.ConfigManager.Get("CloseToIcon");
            if (string.IsNullOrWhiteSpace(isCloseToIconText) == false)
            {
                this.isCloseToIcon = bool.Parse(isCloseToIconText);
            }
            miCloseToIcon.IsChecked = this.isCloseToIcon;

            //搜索范围
            var searchArea = App.ConfigManager.Get("SearchRange");
            if (string.IsNullOrWhiteSpace(searchArea) == false)
            {
                foreach (var item in cmbSearchArea.Items)
                {
                    var ci = item as ComboBoxItem;
                    if (ci == null) continue;

                    if ((ci.Tag as string) == searchArea)
                    {
                        cmbSearchArea.SelectedItem = ci;
                    }
                }
            }

            //窗口状态
            var windowStateText = App.ConfigManager.Get("WindowState");
            if (string.IsNullOrWhiteSpace(windowStateText) == false)
            {
                switch (windowStateText)
                {
                    case "Maximized":
                        {
                            this.WindowState = WindowState.Maximized;
                            break;
                        }
                    default:
                        {
                            this.WindowState = WindowState.Normal;
                            break;
                        }
                }
            }

            //文本尺寸
            var fontSizeText = App.ConfigManager.Get("FontSize");
            if (string.IsNullOrWhiteSpace(fontSizeText) == false)
            {
                double newFontSize;
                if (double.TryParse(fontSizeText, out newFontSize))
                {
                    this.mainTabControl.FontSize = newFontSize;
                    RefreshFontSize();
                }
            }

            //设置高亮显示方案，性能低时选择简单方案，计算机性能强大时选择复杂方案
            var highlightingSettingText = App.ConfigManager.Get("HighlightingSetting");
            if (string.IsNullOrWhiteSpace(highlightingSettingText) == false)
            {
                MarkDownEditorBase.HighlightingType highlight;
                if (Enum.TryParse<MarkDownEditorBase.HighlightingType>(highlightingSettingText, out highlight))
                {
                    this.HighlightingSetting = highlight;
                }
            }

            switch (this.HighlightingSetting)
            {
                case MarkDownEditorBase.HighlightingType.None:
                    {
                        miHighlightingNone.IsChecked = true;
                        miHighlightingAdvance.IsChecked = false;
                        miHighlightingOnlyHeaders.IsChecked = false;
                        break;
                    }
                case MarkDownEditorBase.HighlightingType.OnlyHeaders:
                    {
                        miHighlightingNone.IsChecked = false;
                        miHighlightingAdvance.IsChecked = false;
                        miHighlightingOnlyHeaders.IsChecked = true;
                        break;
                    }
                default:
                    {
                        miHighlightingNone.IsChecked = false;
                        miHighlightingAdvance.IsChecked = true;
                        miHighlightingOnlyHeaders.IsChecked = false;
                        break;
                    }
            }
            //考虑到某些选项必须先载入，所以最后载入上一次打开的那些文档。
            var rememberOpenedFilesConfig = App.ConfigManager.Get("RememberOpenedFiles");
            if (string.IsNullOrEmpty(rememberOpenedFilesConfig) == false)
            {
                bool r;
                if (bool.TryParse(rememberOpenedFilesConfig, out r))
                {
                    this.RememberOpenedFiles = r;
                }
            }
            else
            {
                this.RememberOpenedFiles = true;//默认打开
            }
            miRememberOpenedFiles.IsChecked = this.RememberOpenedFiles;

        }

        /// <summary>
        /// 编译后的 Html 使用的主题配色的标记文本。
        /// </summary>
        public string ThemeText
        {
            get
            {
                if (cmbColor.SelectedItem == null)
                {
                    cmbColor.SelectedIndex = 0;
                    return "light";//默认
                }

                return (cmbColor.SelectedItem as ComboBoxItem).Tag.ToString();
            }
        }

        /// <summary>
        /// 用于刷新树型框列表。
        /// </summary>
        private WorkspaceManager workspaceManager;
        /// <summary>
        /// 工作区管理器。
        /// </summary>
        internal WorkspaceManager WorkspaceManager
        {
            get
            {
                if (this.workspaceManager == null)
                {
                    this.workspaceManager = new WorkspaceManager(this);
                }
                return workspaceManager;
            }
        }

        /// <summary>
        /// 让当前活动编辑器执行“撤销”操作。
        /// </summary>
        public void Undo()
        {
            if (this.mainTabControl.SelectedItem == null) return;

            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            try
            {
                if (eti.EditorBase.CanUndo)
                {
                    eti.EditorBase.Undo();
                }
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        /// <summary>
        /// 让当前活动编辑器执行“重做”操作。
        /// </summary>
        public void Redo()
        {
            if (this.mainTabControl.SelectedItem == null) return;

            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            try
            {
                if (eti.EditorBase.CanRedo)
                {
                    eti.EditorBase.Redo();
                }
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        /// <summary>
        /// 保存当前活动编辑器中的文档。
        /// </summary>
        /// <param name="formatBeforeSave">保存前是否格式化 Markdown 文本。</param>
        private void SaveDocment(bool formatBeforeSave)
        {
            if (this.mainTabControl.SelectedItem == null) return;

            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            if (eti.IsModified == false) return;

            string oldFileFullPath = eti.FullFilePath;

            if (formatBeforeSave)
            {
                //eti.EditorBase.Text = eti.GetFormatedMarkdownText();
                eti.EditorBase.Format();
            }

            if (File.Exists(eti.FullFilePath))
            {
                eti.SaveDocument();
            }
            else
            {
                SaveNewFile(ActivedEditor);
            }

            //自打改走IDE路子以后，下面这段代码几乎已经不可能再起作用了。
            if (eti.FullFilePath != oldFileFullPath)
            {
                //this.workspaceManager.Refresh(eti.FullFilePath);//刷新工作区文件列表。
                var entryItem = FindWorkspaceTreeViewItem(eti.FullFilePath);
                if (entryItem != null)
                {
                    entryItem.RefreshFileState();
                }
            }
        }

        /// <summary>
        /// 仅用以保存不是从“工作区”创建的、且不是从磁盘打开的文件。
        /// </summary>
        private string SaveNewFile(MarkdownEditor editor)
        {
            if (editor == null) return "未指定目标编辑器。";

            WorkspaceTreeViewItem wtvi;
            if (tvWorkDirectory.SelectedItem == null)
            {
                LMessageBox.Show("　　请先在主界面左侧工作区浏览窗口中选定一个目录来创建文件。" +
                    "如果看不到工作区浏览窗口，请按F12键显示左工具栏。",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Information);
                return "未选中保存目录。";
            }
            else
            {
                wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            }

            string directoryPath;
            WorkspaceTreeViewItem destWtvi;

            if (wtvi.IsMarkdownFilePath)
            {
                directoryPath = wtvi.ParentDirectory;
                destWtvi = wtvi.ParentWorkspaceTreeViewItem;
            }
            else
            {
                directoryPath = wtvi.FullPath;
                destWtvi = wtvi;
            }

            if (Directory.Exists(directoryPath) == false)
            {
                var lastIndex = directoryPath.LastIndexOf("\\");
                if (lastIndex < 0)
                {
                    LMessageBox.Show("　　只能在目录下新建文件。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return "指定位置不是目录。";
                }

                var parentDirectory = directoryPath.Substring(0, lastIndex);
                if (Directory.Exists(parentDirectory) == false)
                {
                    LMessageBox.Show("　　只能在目录下新建文件。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return "指定位置不是目录。";
                }

                directoryPath = parentDirectory;
            }

            if (directoryPath.EndsWith("~") || directoryPath.EndsWith("~\\"))
            {
                LMessageBox.Show("　　以波形符结尾的目录是程序自动添加的资源目录，不能在其中再建立MarkDown文件。",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return "指定位置不是合法目录。";
            }

            if (directoryPath.EndsWith("\\") == false) directoryPath += "\\";

            string newShortFileName = InputBox.Show(Globals.AppName, "请输入新文件名（不能以“_”开头）：", "", true,
                   "说明：\r\n　　⑴不需要输入后缀名；\r\n　　⑵请尽可能设定有意义的文件名。\r\n　　⑶不能以下划线开头。\r\n　　⑷尽量以字母开头。\r\n　　因为此文件名很可能将来被其它文件引用。所以，如非必要，创建之后请勿随意更改文件名。");

            if (newShortFileName == null) return "用户放弃保存文件。";//用户放弃新建文件。

            if (newShortFileName.Contains(".") == false) newShortFileName += ".md";

            if (string.IsNullOrEmpty(newShortFileName) || newShortFileName.StartsWith("."))
            {
                LMessageBox.Show("　　文件名称不能为空！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Error);
                return "用户未提供文件名。";
            }

            var newFilePath = directoryPath + newShortFileName;
            if (File.Exists(newFilePath))
            {
                LMessageBox.Show("　　已存在同名文件，无法完成操作！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Error);
                return "已存在同名文件！";
            }

            try
            {
                var docTitle = newShortFileName.EndsWith(".md") ? newShortFileName.Substring(0, newShortFileName.Length - 3) : newShortFileName;
                editor.EditorBase.Save(newFilePath);
                editor.FullFilePath = newFilePath;

                WorkspaceTreeViewItem newtvi = new WorkspaceTreeViewItem(newFilePath, Globals.MainWindow);
                destWtvi.Items.Insert(0, newtvi);
                newtvi.IsSelected = true;

                return string.Empty;
            }
            catch (Exception ex)
            {
                LMessageBox.Show("　　新建文件失败！错误消息：\r\n" + ex.Message + "\r\n" + ex.StackTrace,
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Error);
                return ex.Message;
            }
        }

        /// <summary>
        /// 保存当前打开的所有文档。
        /// </summary>
        private void SaveAllDocumentsAndOptions()
        {
            //啰嗦
            //var result = LMessageBox.Show("　　此操作除保存已修改的文档之外，还会将当前工作区 Html 编译选项和工作区条目状态存盘。\r\n\r\n　　要继续吗？",
            //    Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Question);
            //if (result != MessageBoxResult.Yes) return;

            //TODO: 按下 SaveAll 时，暂时只想到要保存这些东西，以后可以继续添加。
            RememberWorkspaceHtmlCompileOptions();
            WorkspaceManager.SaveWorkspaceTreeviewToXml();

            List<MarkdownEditor> needSavingDocumentList = new List<MarkdownEditor>();
            foreach (var item in this.mainTabControl.Items)
            {
                MarkdownEditor eti = item as MarkdownEditor;
                if (eti != null && eti.IsModified)
                {
                    needSavingDocumentList.Add(eti);
                }
            }

            if (needSavingDocumentList.Count > 0)
            {
                string saveInfo;
                foreach (MarkdownEditor eti in needSavingDocumentList)
                {
                    if (miFormatBeforeSave.IsChecked)
                    {
                        eti.EditorBase.Text = eti.FormatedMarkdownText();
                    }
                    saveInfo = eti.SaveDocument();
                    //this.workspaceManager.Refresh(eti.FullFilePath);//刷新工作区文件列表。
                    var entryItem = FindWorkspaceTreeViewItem(eti.FullFilePath);
                    if (entryItem != null)
                    {
                        entryItem.RefreshFileState();
                    }

                    if (saveInfo == "用户取消操作")
                    {
                        LMessageBox.Show("　　取消了操作。未能保存所有需要保存的文档。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                        return;
                    }
                    else if (saveInfo != string.Empty)
                    {
                        LMessageBox.Show("　　未能保存所有需要保存的文件。错误消息如下：\r\n" + saveInfo, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                        return;//不关闭
                    }
                }
            }

            string shouldSelItemPath = null;
            WorkspaceTreeViewItem wi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (wi != null)
            {
                shouldSelItemPath = wi.FullPath;
            }

            //这个办法效率太低。
            //this.workspaceManager.Refresh(shouldSelItemPath);//刷新整个工作区文件列表。
        }

        /// <summary>
        /// 关闭程序前做些清理、记录工作。
        /// </summary>
        void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            if (isForceExit == false)
            {
                if (this.IsCloseToIcon)
                {
                    windowState = this.WindowState;
                    this.Hide();
                    e.Cancel = true;
                    return;
                }
            }

            List<MarkdownEditor> needSavingDocumentList = new List<MarkdownEditor>();
            foreach (var item in this.mainTabControl.Items)
            {
                MarkdownEditor eti = item as MarkdownEditor;
                if (eti != null && eti.IsModified)
                {
                    needSavingDocumentList.Add(eti);
                }
            }

            if (needSavingDocumentList.Count > 0)
            {
                MessageBoxResult r = LMessageBox.Show(string.Format("　　有 {0} 个文档已被修改，要保存吗？", needSavingDocumentList.Count),
                    Globals.AppName, MessageBoxButton.YesNoCancel, MessageBoxImage.Warning);
                switch (r)
                {
                    case MessageBoxResult.Yes:
                        {
                            string saveInfo;
                            foreach (MarkdownEditor eti in needSavingDocumentList)
                            {
                                saveInfo = eti.SaveDocument();
                                if (saveInfo == "用户取消操作")
                                {
                                    e.Cancel = true;
                                    isForceExit = false;//还原
                                    return;//不关闭
                                }
                                else if (saveInfo != string.Empty)
                                {
                                    LMessageBox.Show(saveInfo, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                                    e.Cancel = true;
                                    isForceExit = false;//还原
                                    return;//不关闭
                                }
                            }

                            break;
                        }
                    case MessageBoxResult.Cancel:
                        {
                            e.Cancel = true;
                            isForceExit = false;//还原
                            return;
                        }
                        //default://No,直接关闭窗口
                }
            }

            RememberOpenedFilesForWorkspace();

            App.ConfigManager.Set("SearchRange", (cmbSearchArea.SelectedItem as ComboBoxItem).Tag.ToString());
            App.ConfigManager.Set("WindowState", this.WindowState.ToString());

            //[废弃]记录工作区管理器中各项的“展开/折叠”状态。已由WorkspaceItems.xml文件接管。
            //SaveWorkspaceCollapseStatus();

            //退出托盘图标
            notifyIcon?.Dispose();

            //记录到Xml文件中下次打开会快一些。
            workspaceManager.SaveWorkspaceTreeviewToXml();
        }

        //[废弃]记录工作区管理器中各项的“展开/折叠”状态。已由WorkspaceItems.xml文件接管。
        //private void SaveWorkspaceCollapseStatus()
        //{
        //    StringBuilder sbuilder = new StringBuilder();
        //    foreach (var item in tvWorkDirectory.Items)
        //    {
        //        var wi = item as WorkspaceTreeViewItem;
        //        if (wi == null) continue;

        //        RememberWorkspaceItemsCollapseStatus(wi, ref sbuilder);
        //    }
        //    App.WorkspaceConfigManager.Set("WorkspaceCollapseStatus", sbuilder.ToString());
        //}

        private void RememberWorkspaceItemsCollapseStatus(WorkspaceTreeViewItem item, ref StringBuilder sbuilder)
        {
            if (item == null) return;

            var fullPath = item.FullPath.EndsWith("\\") ? item.FullPath : (item.FullPath + "\\");

            sbuilder.Append($"{fullPath}|{item.IsExpanded.ToString()}||");

            foreach (var subItem in item.Items)
            {
                var subwi = subItem as WorkspaceTreeViewItem;
                if (subwi == null) continue;

                RememberWorkspaceItemsCollapseStatus(subwi, ref sbuilder);
            }
        }

        /// <summary>
        /// 记录所有当前打开的文档的路径，以便下次启动程序时自动打开。
        /// </summary>
        private void RememberOpenedFilesForWorkspace()
        {
            if (this.RememberOpenedFiles)
            {
                //重置工作区配置文件管理器
                App.WorkspaceConfigManager = null;

                StringBuilder sb = new StringBuilder();
                foreach (var item in this.mainTabControl.Items)
                {
                    MarkdownEditor me = item as MarkdownEditor;
                    if (me == null || File.Exists(me.FullFilePath) == false) continue;

                    sb.Append(me.FullFilePath);
                    sb.Append(";");
                }

                App.WorkspaceConfigManager.Set("OpenedFiles", sb.ToString());

                if (this.mainTabControl.SelectedItem == null)
                {
                    App.WorkspaceConfigManager.Set("ActiveDocumentFullPath", "");
                }
                else
                {
                    var editor = this.mainTabControl.SelectedItem as MarkdownEditor;
                    if (editor == null)
                    {
                        App.WorkspaceConfigManager.Set("ActiveDocumentFullPath", "");
                    }
                    else
                    {
                        App.WorkspaceConfigManager.Set("ActiveDocumentFullPath", editor.FullFilePath);
                    }
                }
            }
        }

        /// <summary>
        /// 处理不会在工作区管理器和编辑器操作之间产生冲突的快捷键。
        /// 处理可能存在冲突，但是工作区管理器不需要使用或需要屏蔽其功能的快捷键。
        /// </summary>
        void MainWindow_PreviewKeyDown(object sender, KeyEventArgs e)
        {
            KeyStates ksRShift = Keyboard.GetKeyStates(Key.RightShift);
            KeyStates ksLShift = Keyboard.GetKeyStates(Key.LeftShift);
            KeyStates ksLAlt = Keyboard.GetKeyStates(Key.LeftAlt);
            KeyStates ksRAlt = Keyboard.GetKeyStates(Key.RightAlt);
            KeyStates ksLCtrl = Keyboard.GetKeyStates(Key.LeftCtrl);
            KeyStates ksRCtrl = Keyboard.GetKeyStates(Key.RightCtrl);

            bool isCtrl, isShift, isAlt;

            isShift = (ksLShift & KeyStates.Down) > 0 || (ksRShift & KeyStates.Down) > 0;
            isCtrl = (ksLCtrl & KeyStates.Down) > 0 || (ksRCtrl & KeyStates.Down) > 0;
            isAlt = (ksLAlt & KeyStates.Down) > 0 || (ksRAlt & KeyStates.Down) > 0;
            switch (e.Key)
            {
                case Key.N:
                    {
                        if (isCtrl && isShift == false && isAlt == false)
                        {
                            NewFile(false);
                            e.Handled = true;
                        }
                        break;
                    }
                case Key.T:
                    {
                        if (isCtrl)
                        {
                            if (isShift)//Ctrl+Shift+T
                            {
                                FormatAsTextTable();
                                e.Handled = true;//否则会插入一个字母t
                            }
                            else
                            {
                                if (isAlt == false)//Ctrl+T
                                {
                                    NewFile(false);
                                    e.Handled = true;
                                }
                            }
                        }
                        break;
                    }
                case Key.S:
                    {
                        if (isCtrl && isAlt == false)
                        {
                            if (isShift)
                            {
                                SaveAllDocumentsAndOptions();
                                e.Handled = true;
                            }
                            else
                            {
                                SaveDocment(miFormatBeforeSave.IsChecked);
                                e.Handled = true;
                            }
                        }
                        break;
                    }
                case Key.O:
                    {
                        if (isCtrl && isShift == false && isAlt == false)
                        {
                            miOpenDocument_Click(sender, e);
                            e.Handled = true;
                        }
                        break;
                    }
                case Key.OemPlus:
                    {
                        if (isCtrl && isAlt == false)
                        {
                            if (isShift)
                            {
                                FontSizeUp();
                                e.Handled = true;
                            }
                            else
                            {
                                var selEditor = this.mainTabControl.SelectedItem as MarkdownEditor;
                                if (selEditor != null)
                                {
                                    selEditor.EditorBase.FindNextHeader();
                                    e.Handled = true;
                                }
                            }
                        }
                        break;
                    }
                case Key.OemMinus:
                    {
                        if (isCtrl && isAlt == false)
                        {
                            if (isShift)
                            {
                                FontSizeDown();
                                e.Handled = true;
                            }
                            else
                            {
                                var selEditor = this.mainTabControl.SelectedItem as MarkdownEditor;
                                if (selEditor != null)
                                {
                                    selEditor.EditorBase.FindPreviewHeader();
                                    e.Handled = true;
                                }
                            }
                        }
                        break;
                    }
                case Key.D0:
                    {
                        if (isCtrl && isShift == false && isAlt == false)
                        {
                            this.mainTabControl.FontSize = 16;//默认字号16。
                            App.ConfigManager.Set("FontSize", this.mainTabControl.FontSize.ToString());
                            RefreshFontSize();
                            e.Handled = true;
                        }
                        break;
                    }
                case Key.F1:
                    {
                        if (!isAlt)
                        {
                            if (!isShift)
                            {
                                if (isCtrl)
                                {
                                    btnResetLeftToolbarLayout_MouseLeftButtonDown(null, null);
                                    e.Handled = true;
                                }
                                else
                                {
                                    SwitchLeftToolBarToggle();
                                }
                            }
                        }
                        else
                        {
                            if (isCtrl)//Ctrl+Alt+F1显示帮助
                            {
                                ShowHelp();
                            }
                        }
                        break;
                    }
                case Key.U:
                    {
                        if (isCtrl && !isAlt)
                        {
                            if (isShift)
                            {
                                WrapWithSTag();
                            }
                            else
                            {
                                WrapWithUTag();
                            }
                            e.Handled = true;
                        }
                        break;
                    }
                case Key.F5:
                    {
                        if (!isCtrl)
                        {
                            if (!isAlt)
                            {
                                if (isShift)
                                {
                                    cmbPerspective.SelectedIndex = (int)Perspective.FullScreenPreview;
                                }

                                CompileAndPreviewHtml();
                                e.Handled = true;
                            }
                        }
                        else
                        {
                            if (!isAlt)
                            {
                                if (!isShift)
                                {
                                    CompileAndPresentateHtml(CustomMarkdownSupport.PresentateHtmlSplitterType.ByDocument);
                                    e.Handled = true;
                                }
                            }
                        }
                        break;
                    }
                case Key.F9:
                    {
                        MainTabControl_ItemListButtonClicked(sender, null);
                        e.Handled = true;
                        break;
                    }
                case Key.F11:
                    {
                        if (!isAlt && !isShift && !isCtrl)
                        {
                            cmbPerspective.IsDropDownOpen = !cmbPerspective.IsDropDownOpen;
                        }

                        //if (!isCtrl && !isAlt)
                        //{
                        //    if (cmbPerspective.SelectedIndex == (int)Perspective.FullScreenEditing ||
                        //        cmbPerspective.SelectedIndex == (int)Perspective.FullScreenPreview)
                        //    {
                        //        cmbPerspective.SelectedIndex = (int)Perspective.Normal;
                        //    }
                        //    else
                        //    {
                        //        if (isShift)
                        //            cmbPerspective.SelectedIndex = (int)Perspective.FullScreenEditing;
                        //        else
                        //            cmbPerspective.SelectedIndex = (int)Perspective.EditingAndPreview;
                        //    }
                        //    e.Handled = true;
                        //}
                        //else
                        //{
                        //    if (!isShift && !isAlt)
                        //    {
                        //        //Ctrl+F11,开启“演讲者模式”，即重排左工具栏、主编辑区、右工具栏的宽度
                        //        if (cmbPerspective.SelectedIndex == (int)Perspective.EditingAndPresentation)
                        //        {
                        //            ShowPresentatorMode();//刷新一下是有必要的。
                        //        }
                        //        else
                        //        {
                        //            cmbPerspective.SelectedIndex = (int)Perspective.EditingAndPresentation;
                        //            //也会调用ShowPresentatorMode();
                        //        }
                        //        e.Handled = true;
                        //    }
                        //}
                        break;
                    }
                case Key.F12:
                    {
                        if (!isCtrl)
                        {
                            if (!isAlt)
                            {
                                if (isShift == false)
                                {
                                    if (cmbPerspective.SelectedIndex == (int)Perspective.FullScreenPreview)
                                    {
                                        cmbPerspective.SelectedIndex = (int)Perspective.Normal;//退出全屏预览
                                    }
                                    else SwitchRightToolBarToggle();
                                }
                                else
                                {
                                    cmbPerspective.SelectedIndex = (int)Perspective.FullScreenPreview;
                                }
                            }
                        }
                        else
                        {
                            if (!isShift && !isAlt)
                            {
                                //Ctrl+F12
                                miPreviewWholeDocument_Click(sender, e);
                            }
                        }
                        break;
                    }
                case Key.D:
                    {
                        var ae = ActivedEditor;
                        if (isShift && isCtrl && !isAlt)
                        {
                            InsertDateText();
                        }
                        break;
                    }
                case Key.G:
                    {
                        if (isCtrl && !isShift && !isAlt)
                        {
                            //还是给个窗口让用户选择是跳转到：
                            //链接（网址 图像链接 Markdown链接）/标题/TODO标记/任务列表

                            ContextMenu jumpContextMenu = new ContextMenu()
                            {
                                FontFamily = this.FontFamily,
                                FontSize = 14,
                                SnapsToDevicePixels = true,
                                Style = TryFindResource("MetroContextMenu") as Style,
                            };
                            TextOptions.SetTextFormattingMode(jumpContextMenu, TextFormattingMode.Display);

                            //ControlTemplate SubMenuItemControlTemplate = Globals.MainWindow.TryFindResource("SubMenuItemControlTemplate") as ControlTemplate;
                            //ControlTemplate MidMenuItemControlTemplate = Globals.MainWindow.TryFindResource("MidMenuItemControlTemplate") as ControlTemplate;
                            //ControlTemplate MenuItemControlTemplate = Globals.MainWindow.TryFindResource("MenuItemControlTemplate") as ControlTemplate;

                            MenuItem mciPreviewImage = new MenuItem()
                            {
                                Header = "预览图像(_G)",
                                ToolTip = "在左工具栏图像预览区预览图像",
                                Style = TryFindResource("MetroMenuItem") as Style,
                            };
                            mciPreviewImage.Click += MciPreviewImage_Click;
                            jumpContextMenu.Items.Add(mciPreviewImage);

                            MenuItem mciGotoLink = new MenuItem()
                            {
                                //[包含图像链接]
                                Header = "跳转到链接(_L)",
                                ToolTip = "包括 MD 文件、图像、网址等",
                                Style = TryFindResource("MetroMenuItem") as Style,
                            };
                            mciGotoLink.Click += MciGotoLink_Click;
                            jumpContextMenu.Items.Add(mciGotoLink);

                            MenuItem mciGotoHeader = new MenuItem()
                            {
                                Header = "跳转到标题(_H)",
                                Style = TryFindResource("MetroMenuItem") as Style,
                            };
                            mciGotoHeader.Click += MciGotoHeader_Click;
                            jumpContextMenu.Items.Add(mciGotoHeader);

                            MenuItem mciGotoTask = new MenuItem()
                            {
                                Header = "跳转到任务项(_T)",
                                Style = TryFindResource("MetroMenuItem") as Style,
                            };
                            mciGotoTask.Click += MciGotoTask_Click;
                            jumpContextMenu.Items.Add(mciGotoTask);

                            jumpContextMenu.Items.Add(new Separator());

                            MenuItem mciGotoTodoComment = new MenuItem()
                            {
                                Header = "跳转到 TODO 注释(_D)",
                                Tag = "TODO",
                                Style = TryFindResource("MetroMenuItem") as Style,
                            };

                            mciGotoTodoComment.Click += MciGotoTodoComment_Click;
                            jumpContextMenu.Items.Add(mciGotoTodoComment);

                            MenuItem mciGotoDoingComment = new MenuItem()
                            {
                                Header = "跳转到 DOING 注释(_I)",
                                Tag = "DOING",
                                Style = TryFindResource("MetroMenuItem") as Style,
                            };
                            mciGotoDoingComment.Click += MciGotoTodoComment_Click;//DOING/DONE也属于TODO型标签
                            jumpContextMenu.Items.Add(mciGotoDoingComment);

                            MenuItem mciGotoDoneComment = new MenuItem()
                            {
                                Header = "跳转到 DONE 注释(_N)",
                                Tag = "DONE",
                                Style = TryFindResource("MetroMenuItem") as Style,
                            };
                            mciGotoDoneComment.Click += MciGotoTodoComment_Click;//DOING/DONE也属于TODO型标签
                            jumpContextMenu.Items.Add(mciGotoDoneComment);

                            var edit = ActivedEditor;
                            if (edit != null)
                            {
                                var line = edit.EditorBase.Document.GetLineByOffset(edit.EditorBase.SelectionStart);
                                var lineText = edit.EditorBase.Document.GetText(line.Offset, line.Length);

                                if (CustomMarkdownSupport.IsTodoCommentLine(lineText))
                                {
                                    mciGotoTodoComment.Focus();
                                }
                                else if (CustomMarkdownSupport.IsDateLine(lineText) ||
                                   CustomMarkdownSupport.IsTaskLine(lineText))
                                {
                                    mciGotoTask.Focus();
                                }
                                else
                                {

                                }
                            }

                            jumpContextMenu.Placement = PlacementMode.Center;
                            jumpContextMenu.PlacementTarget = this;
                            jumpContextMenu.IsOpen = true;

                            e.Handled = true;
                        }
                        break;
                    }
                case Key.E:
                    {
                        if (isCtrl && !isAlt)
                        {
                            if (isShift)
                            {
                                miIsAutoCompletionEnabled_Clicked(sender, e);
                            }
                            else
                            {
                                miIsExamEnabled_Click(sender, e);
                            }
                        }
                        break;
                    }
                case Key.R:
                    {
                        if (isCtrl && !isAlt)
                        {
                            if (isShift)
                            {
                                miWrapWithRegionMark_Click(sender, e);
                            }
                            //2016年11月25日，将Ctrl+R改到各编辑器自身临时切换，不再统一切换。
                            //else
                            //{
                            //    miTextWrap_Click(sender, e);
                            //}
                        }
                        break;
                    }
                case Key.K:
                    {
                        if (isCtrl && !isAlt && !isShift)
                        {
                            miInsertLinkMark_Click(sender, e);
                        }
                        break;
                    }
                case Key.F:
                    {
                        if (isCtrl)
                        {
                            if (isShift)
                            {
                                if (isAlt == false)
                                {
                                    //Ctrl+Shift+F
                                    Format();
                                }
                                e.Handled = true;
                            }
                            else
                            {
                                if (isAlt)
                                {
                                    //Ctrl+Alt+F
                                    if (rdFindAndReplace.ActualHeight <= 20)
                                    {
                                        rdFindAndReplace.Height = new GridLength(0, GridUnitType.Auto);
                                        cmbFindText.UpdateLayout();
                                    }

                                    var editor = ActivedEditor;
                                    if (editor != null)
                                    {
                                        cmbFindText.Text = editor.EditorBase.SelectedText;
                                    }
                                    else return;

                                    cmbSearchArea.SelectedIndex = 1;

                                    cmbFindText.Focus();

                                    if (cmbFindText.Text.Length > 0)
                                    {
                                        FindText(tvFindAndReplace);
                                    }

                                    cmbFindText.Focus();

                                    if (cdRightToolsArea.ActualWidth < 100)
                                    {
                                        cdMainEditArea.Width =
                                            cdRightToolsArea.Width = new GridLength(3, GridUnitType.Star);
                                    }

                                    if (tcRightToolBar.SelectedIndex != 1)
                                    {
                                        tcRightToolBar.SelectedIndex = 1;
                                    }

                                    e.Handled = true;
                                }
                                //else
                                //{
                                //    //Ctrl+F
                                //    //如果放在这里，会导致在Html预览区域按“Ctrl+F”无法弹出查找框。
                                //    //所以这个功能必须放在编辑区中才好。
                                //}
                            }
                        }
                        break;
                    }
                case Key.H:
                    {
                        // Ctrl+H
                        if (isCtrl && !isAlt && !isShift)
                        {
                            if (rdFindAndReplace.ActualHeight <= 40)
                            {
                                rdFindAndReplace.Height = new GridLength(140, GridUnitType.Auto);
                                cmbFindText.UpdateLayout();
                            }

                            var editor = ActivedEditor;
                            if (editor != null)
                            {
                                cmbFindText.Text = editor.EditorBase.SelectedText;
                            }
                            else return;

                            cmbSearchArea.SelectedIndex = 0;

                            if (cmbFindText.Text.Length > 0)
                            {
                                cmbReplaceTextInputBox.Focus();
                            }
                            else
                            {
                                cmbFindText.Focus();
                            }
                            e.Handled = true;
                        }
                        break;
                    }
                case Key.L:
                    {
                        if (isCtrl && isShift == false && isAlt == false)
                        {
                            SelecetLine();
                            e.Handled = true;
                        }
                        break;
                    }
                case Key.F3:
                    {
                        if (isCtrl == false && isAlt == false)
                        {
                            if (isShift)
                            {
                                if (cmbFindText.Text.Length > 0)
                                {
                                    cbSearchUp.IsChecked = true;
                                    FindNext(cmbFindText.Text);
                                }
                            }
                            else
                            {
                                if (cmbFindText.Text.Length > 0)
                                {
                                    cbSearchUp.IsChecked = false;
                                    FindNext(cmbFindText.Text);
                                }
                            }
                            e.Handled = true;
                        }
                        break;
                    }
                case Key.Escape:
                    {
                        if (isCtrl == false && isShift == false && isAlt == false)
                        {
                            var efi = this.mainTabControl.SelectedItem as MarkdownEditor;
                            if (efi != null) // && efi.EditorBase.IsSearchPanelOpened)
                            {
                                if (efi.EditorBase.CompletionWindow != null &&
                                    efi.EditorBase.CompletionWindow.Visibility == Visibility.Visible)
                                {
                                    efi.EditorBase.CompletionWindow.Close();
                                }

                                //CloseSearchPanel();//AvalonEdit的SearchPanel总是有些问题，放弃了
                                if (rdFindAndReplace.ActualHeight > 0)
                                {
                                    rdFindAndReplace.Height = new GridLength(0);
                                    FocusActiveEditor();
                                    e.Handled = true;
                                    break;
                                }
                            }

                            //之前这个键用于退出全屏状态
                            //但后来，发现经常在编辑时用到（例如关闭查找框、退出自动完成），
                            //一按就切换视图，不好。
                            switch (this.PerspectiveMode)
                            {
                                case Perspective.FullScreenPreview:
                                case Perspective.EditingAndPresentation:
                                    {
                                        cmbPerspective.SelectedIndex = (int)Perspective.Normal;
                                        e.Handled = true;
                                        break;
                                    }
                            }
                        }
                        break;
                    }
                case Key.OemTilde://波形符键（反引号键）
                    {
                        if (isCtrl)
                        {
                            var mei = this.mainTabControl.SelectedItem as MarkdownEditor;
                            if (mei != null)
                            {
                                mei.EditorBase.WrapTextWithAntiQuotes();
                            }
                        }
                        break;
                    }
                case Key.OemQuotes:
                    {
                        if (isCtrl && isAlt == false)
                        {
                            var mei = this.mainTabControl.SelectedItem as MarkdownEditor;
                            if (mei != null)
                            {
                                mei.EditorBase.WrapTextWithQuotes(isShift);
                            }
                        }
                        break;
                    }
                case Key.OemPipe:
                    {
                        if (isCtrl && isShift && !isAlt)
                        {
                            //插入新列
                            miInsertNewColumn_Click(sender, e);
                        }
                        break;
                    }
                case Key.Q:
                    {
                        //定位到资源搜索框，这样就可以直接输入文本了。
                        if (isCtrl && !isShift && !isAlt)
                        {
                            cmbSearchResource.Focus();
                        }
                        break;
                    }
                case Key.F6:
                    {
                        if (!isCtrl && !isAlt && !isShift)
                        {
                            //演示试题
                            PresentationExams();
                        }
                        break;
                    }
                case Key.D1:
                    {
                        if (isCtrl)
                        {
                            if (isAlt && !isShift)
                            {
                                ChangeWorkspaceByShortCut(isCtrl, isShift, isAlt, 1);
                                e.Handled = true;
                            }
                            else
                            {
                                if (!isShift)
                                {
                                    //Ctrl+数字
                                    SwitchTitleLevel(1);
                                }
                            }
                        }
                        else
                        {
                            if (!isAlt && !isShift)
                            {
                                if (cmbPerspective.IsDropDownOpen)
                                {
                                    cmbPerspective.SelectedIndex = 0;
                                    cmbPerspective.IsDropDownOpen = false;
                                    var activeEditor = ActivedEditor;
                                    if (activeEditor != null) activeEditor.EditorBase.TextArea.Focus();
                                    e.Handled = true;
                                }
                            }
                        }
                        break;
                    }
                case Key.D2:
                    {
                        if (isCtrl)
                        {
                            if (isAlt && !isShift)
                            {
                                ChangeWorkspaceByShortCut(isCtrl, isShift, isAlt, 2);
                                e.Handled = true;
                            }
                            else
                            {
                                if (!isShift)
                                {
                                    //Ctrl+数字
                                    SwitchTitleLevel(2);
                                }
                            }
                        }
                        else
                        {
                            if (!isAlt && !isShift)
                            {
                                if (cmbPerspective.IsDropDownOpen)
                                {
                                    cmbPerspective.SelectedIndex = 1;
                                    cmbPerspective.IsDropDownOpen = false;
                                    var activeEditor = ActivedEditor;
                                    if (activeEditor != null) activeEditor.EditorBase.TextArea.Focus();
                                    e.Handled = true;
                                }
                            }
                        }
                        break;
                    }
                case Key.D3:
                    {
                        if (isCtrl)
                        {
                            if (isAlt && !isShift)
                            {
                                ChangeWorkspaceByShortCut(isCtrl, isShift, isAlt, 3);
                                e.Handled = true;
                            }
                            else
                            {
                                if (!isShift)
                                {
                                    //Ctrl+数字
                                    SwitchTitleLevel(3);
                                }
                            }
                        }
                        else
                        {
                            if (!isAlt && !isShift)
                            {
                                if (cmbPerspective.IsDropDownOpen)
                                {
                                    cmbPerspective.SelectedIndex = 2;
                                    cmbPerspective.IsDropDownOpen = false;
                                    var activeEditor = ActivedEditor;
                                    if (activeEditor != null) activeEditor.EditorBase.TextArea.Focus();
                                    e.Handled = true;
                                }
                            }
                        }
                        break;
                    }
                case Key.D4:
                    {
                        if (isCtrl)
                        {
                            if (isAlt && !isShift)
                            {
                                ChangeWorkspaceByShortCut(isCtrl, isShift, isAlt, 4);
                                e.Handled = true;
                            }
                            else
                            {
                                if (!isShift)
                                {
                                    //Ctrl+数字
                                    SwitchTitleLevel(4);
                                }
                            }
                        }
                        else
                        {
                            if (!isAlt && !isShift)
                            {
                                if (cmbPerspective.IsDropDownOpen)
                                {
                                    cmbPerspective.SelectedIndex = 3;
                                    cmbPerspective.IsDropDownOpen = false;
                                    var activeEditor = ActivedEditor;
                                    if (activeEditor != null) activeEditor.EditorBase.TextArea.Focus();
                                    e.Handled = true;
                                }
                            }
                        }
                        break;
                    }
                case Key.D5:
                    {
                        if (isCtrl)
                        {
                            if (isAlt && !isShift)
                            {
                                ChangeWorkspaceByShortCut(isCtrl, isShift, isAlt, 5);
                                e.Handled = true;
                            }
                            else
                            {
                                if (!isShift)
                                {
                                    //Ctrl+数字
                                    SwitchTitleLevel(5);
                                }
                            }
                        }
                        else
                        {
                            if (!isAlt && !isShift)
                            {
                                if (cmbPerspective.IsDropDownOpen)
                                {
                                    cmbPerspective.SelectedIndex = 4;
                                    cmbPerspective.IsDropDownOpen = false;
                                    var activeEditor = ActivedEditor;
                                    if (activeEditor != null) activeEditor.EditorBase.TextArea.Focus();
                                    e.Handled = true;
                                }
                            }
                        }
                        break;
                    }
                case Key.D6:
                    {
                        if (isCtrl)
                        {
                            if (isAlt && !isShift)
                            {
                                ChangeWorkspaceByShortCut(isCtrl, isShift, isAlt, 6);
                                e.Handled = true;
                            }
                            else
                            {
                                if (!isShift)
                                {
                                    //Ctrl+数字
                                    SwitchTitleLevel(6);
                                }
                            }
                        }
                        else
                        {
                            if (!isAlt && !isShift)
                            {
                                if (cmbPerspective.IsDropDownOpen)
                                {
                                    cmbPerspective.SelectedIndex = 5;
                                    cmbPerspective.IsDropDownOpen = false;
                                    var activeEditor = ActivedEditor;
                                    if (activeEditor != null) activeEditor.EditorBase.TextArea.Focus();
                                    e.Handled = true;
                                }
                            }
                        }
                        break;
                    }
                case Key.D7:
                    {
                        if (isCtrl)
                        {
                            if (!isShift)
                            {
                                if (isAlt)
                                {
                                    ChangeWorkspaceByShortCut(isCtrl, isShift, isAlt, 7);
                                    e.Handled = true;
                                }
                                else
                                {
                                    miInsertHorizontalLine_Click(miInsertHorizontal, e);
                                    e.Handled = true;
                                }
                            }
                        }
                        else
                        {
                            if (!isAlt && !isShift)
                            {
                                if (cmbPerspective.IsDropDownOpen)
                                {
                                    cmbPerspective.SelectedIndex = 6;
                                    cmbPerspective.IsDropDownOpen = false;
                                    var activeEditor = ActivedEditor;
                                    if (activeEditor != null) activeEditor.EditorBase.TextArea.Focus();
                                    e.Handled = true;
                                }
                            }
                        }
                        break;
                    }
                case Key.D8:
                    {
                        if (isCtrl)
                        {
                            if (isAlt && !isShift)
                            {
                                ChangeWorkspaceByShortCut(isCtrl, isShift, isAlt, 8);
                                e.Handled = true;
                            }
                        }
                        else
                        {
                            if (!isAlt && !isShift)
                            {
                                if (cmbPerspective.IsDropDownOpen)
                                {
                                    cmbPerspective.SelectedIndex = 7;             //切换到对照模式
                                    cmbPerspective.IsDropDownOpen = false;

                                    //如果没有更改cmbPerspective.SelectedIndex，刷新对照区仍然是必要的
                                    var activeEditor = ActivedEditor;
                                    if (activeEditor != null)
                                    {
                                        activeEditor.SendToRightCompareArea();
                                        activeEditor.EditorBase.TextArea.Focus();
                                    }
                                    e.Handled = true;
                                }
                            }
                        }
                        break;
                    }
                case Key.D9:
                    {
                        if (isCtrl)
                        {
                            if (isAlt && !isShift)
                            {
                                ChangeWorkspaceByShortCut(isCtrl, isShift, isAlt, 9);
                                e.Handled = true;
                            }
                        }
                        else
                        {
                            if (!isAlt && !isShift)
                            {
                                if (cmbPerspective.IsDropDownOpen)
                                {
                                    cmbPerspective.SelectedIndex = 8;             //切换到迷你模式
                                    cmbPerspective.IsDropDownOpen = false;

                                    var activeEditor = ActivedEditor;
                                    if (activeEditor != null) activeEditor.EditorBase.TextArea.Focus();
                                    e.Handled = true;
                                }
                            }
                        }
                        break;
                    }
            }
        }

        /// <summary>
        /// 查找所有 TODO Comment，以便跳转到某个 TODO Comment。
        /// （也可以是“DONE”、“DOING”。）
        /// </summary>
        private void MciGotoTodoComment_Click(object sender, RoutedEventArgs e)
        {
            var mark = (sender as MenuItem).Tag.ToString();
            GotoTodoComment(mark);
        }

        /// <summary>
        /// 查找所有任务列表，以便快速向某个任务列表跳转。
        /// </summary>
        private void MciGotoTask_Click(object sender, RoutedEventArgs e)
        {
            GotoTaskListItem();
        }

        /// <summary>
        /// 查找所有标题，以便快速向某个标题跳转。
        /// </summary>
        private void MciGotoHeader_Click(object sender, RoutedEventArgs e)
        {
            GotoHeader();
        }

        /// <summary>
        /// 在工具栏左侧图像预览区域预览当前 Markdown 链接文本指向的图像文件。
        /// </summary>
        private void MciPreviewImage_Click(object sender, RoutedEventArgs e)
        {
            PreviewImage();
        }

        /// <summary>
        /// 在工具栏左侧图像预览区域预览当前 Markdown 链接文本指向的图像文件。
        /// </summary>
        public void PreviewImage()
        {
            var edit = this.ActivedEditor;
            if (edit == null) return;

            if (edit.GotoAnchorInSameDocument()) return;

            var line = edit.EditorBase.Document.GetLineByOffset(edit.EditorBase.SelectionStart);
            var lineText = edit.EditorBase.Document.GetText(line.Offset, line.EndOffset - line.Offset);
            var startIndex = edit.EditorBase.SelectionStart - line.Offset;

            var leftIndex = lineText.IndexOf("](") + 1;
            var rightIndex = lineText.IndexOf(')', startIndex);
            if (leftIndex <= 0 || rightIndex <= 1 || rightIndex <= leftIndex + 1) return;

            var subText = lineText.Substring(leftIndex + 1, rightIndex - leftIndex - 1);

            if (subText.ToLower().StartsWith("file:///"))
            {
                //文件绝对链接。
                LMessageBox.Show("不是有效图像文件链接，无法预览！",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }

            if (subText.ToLower().Contains(".html"))
            {
                //文件绝对链接。
                LMessageBox.Show("不是有效图像文件链接，无法预览！",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }
            else
            {
                try
                {
                    edit.PreviewLinkedImageFile(false);
                }
                catch (Exception ex)
                {
                    var lower = subText.ToLower();

                    if (lower.EndsWith(".jpg") || lower.EndsWith(".jpeg") || lower.EndsWith(".gif") ||
                        lower.EndsWith(".png") || lower.EndsWith("bmp"))
                    {
                        edit.PreviewLinkedImageFile(false);
                    }
                    else
                    {
                        LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace);
                    }
                }
            }
        }

        /// <summary>
        /// 跳转到当前活动编辑器中当前插入点位置处的链接（图像链接/文档链接/网址）指向的对象。
        /// </summary>
        private void MciGotoLink_Click(object sender, RoutedEventArgs e)
        {
            GotoLink();
        }

        /// <summary>
        /// 查找所有标题，以便快速向某个标题跳转。
        /// </summary>
        private void GotoHeader()
        {
            var gotoWindow = new GotoWindow()
            {
                WindowStartupLocation = WindowStartupLocation.CenterOwner,
                Owner = this,
                Title = Globals.AppName + " - 在标题间跳转",
            };

            FindHeaders(@"^ {0,3}#{1,6}.*", gotoWindow.treeView);
            gotoWindow.ShowDialog();
        }

        /// <summary>
        /// 查找所有任务列表，以便快速向某个任务列表跳转。
        /// </summary>
        private void GotoTaskListItem()
        {
            var gotoWindow = new GotoWindow()
            {
                WindowStartupLocation = WindowStartupLocation.CenterOwner,
                Owner = this,
                Title = Globals.AppName + " - 在任务列表间跳转",
            };

            FindTaskList("", gotoWindow.treeView);
            gotoWindow.ShowDialog();
        }

        /// <summary>
        /// 查找所有 TODO Comment，以便跳转到某个 TODO Comment。
        /// </summary>
        /// <param name="mark">传入“TODO”（或“DOING”、“DONE”）。</param>
        private void GotoTodoComment(string mark)
        {
            var gotoWindow = new GotoWindow()
            {
                WindowStartupLocation = WindowStartupLocation.CenterOwner,
                Owner = this,
                Title = Globals.AppName + " - 在 TODO 标记间跳转",
            };

            FindTodoComment((cmbSearchArea.SelectedItem as ComboBoxItem).Tag.ToString(), gotoWindow.treeView, mark);
            gotoWindow.ShowDialog();
        }

        /// <summary>
        /// 跳转到当前活动编辑器中当前插入点位置处的链接（图像链接/文档链接/网址）指向的对象。
        /// </summary>
        private void GotoLink()
        {
            var edit = this.ActivedEditor;
            if (edit == null) return;

            if (edit.GotoAnchorInSameDocument()) return;

            var line = edit.EditorBase.Document.GetLineByOffset(edit.EditorBase.SelectionStart);
            var lineText = edit.EditorBase.Document.GetText(line.Offset, line.EndOffset - line.Offset);
            var startIndex = edit.EditorBase.SelectionStart - line.Offset;

            var leftIndex = lineText.IndexOf("](") + 1;
            var rightIndex = lineText.IndexOf(')', startIndex);
            if (leftIndex <= 0 || rightIndex <= 1 || rightIndex <= leftIndex + 1) return;

            var subText = lineText.Substring(leftIndex + 1, rightIndex - leftIndex - 1);

            Regex regTail = new Regex(" {1,}[\"].*[\"]$");
            var matchTail = regTail.Match(subText);
            if (matchTail.Success)
            {
                subText = subText.Substring(0, matchTail.Index);
            }

            if (subText.ToLower().StartsWith("file:///"))
            {
                //文件绝对链接。
                var absoluteFullPath = subText.Substring(8);
                if (File.Exists(absoluteFullPath) || Directory.Exists(absoluteFullPath))
                {
                    System.Diagnostics.Process.Start("explorer.exe", $"\"{absoluteFullPath}\"");
                }
                else
                {
                    LMessageBox.Show("不是有效链接，无法打开！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Error);
                }
                return;
            }

            var lowerSubText = subText.ToLower();
            if (lowerSubText.Contains(".html") && lowerSubText.StartsWith("http://") == false &&
                lowerSubText.StartsWith("https://") == false && lowerSubText.StartsWith("ftp://") == false)
            {
                edit.OpenLinkedMarkdownFile();
            }
            else
            {
                try
                {
                    Regex urlReg = new Regex(@"([\w-]+\.)+[\w-]+(/[\w- ./?%&=]*)?");
                    var matchUrl = urlReg.Match(subText);

                    if (matchUrl.Success)
                    {
                        var tmp = subText.ToLower();
                        if (tmp.StartsWith("http://") == false &&
                            tmp.StartsWith("https://") == false &&
                            tmp.StartsWith("ftp://") == false)
                        {
                            tmp = "http://" + subText;
                        }

                        previewFrame.Source = new Uri(tmp);
                        tcRightToolBar.SelectedItem = tiHtmlPreview;//显示预览页
                        return;
                    }
                    else edit.PreviewLinkedImageFile();
                }
                catch (Exception ex)
                {
                    var lower = subText.ToLower();

                    if (lower.EndsWith(".jpg") || lower.EndsWith(".jpeg") || lower.EndsWith(".gif") ||
                        lower.EndsWith(".png") || lower.EndsWith("bmp"))
                    {
                        var imageTitle = edit.PreviewLinkedImageFile(true);
                        if (string.IsNullOrEmpty(imageTitle))
                        {
                            LMessageBox.Show("貌似不是个正确的图像链接！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                        }
                    }
                    else
                    {
                        LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace);
                    }
                }
            }
        }

        /// <summary>
        /// 切换当前活动编辑器中当前行的标题层级。
        /// </summary>
        /// <param name="newLevel">新标题层级</param>
        private void SwitchTitleLevel(int newLevel)
        {
            var activeEditor = this.ActivedEditor;
            if (activeEditor == null) return;

            activeEditor.EditorBase.SwitchTitleLevel(newLevel);
        }

        /// <summary>
        /// 用快捷键切换当前工作区。通常是使用 Ctrl+Shift+Alt+数字键。
        /// </summary>
        /// <param name="isCtrl">Ctrl键状态。</param>
        /// <param name="isShift">Shift键状态。</param>
        /// <param name="isAlt">Alt键状态。</param>
        /// <param name="num">历史工作区列表中工作区条目的序号（1-9).</param>
        private void ChangeWorkspaceByShortCut(bool isCtrl, bool isShift, bool isAlt, int num)
        {
            if (isAlt && isCtrl && !isShift)
            {
                var index = num - 1;
                if (index < 0 || index >= lbxHistoryWorkspaces.Items.Count)
                {
                    LMessageBox.Show("　　历史工作区中没有对应条目！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Information);
                    return;
                }

                lbxHistoryWorkspaces.SelectedIndex = index;

                if (tcManagerPanels.SelectedItem != tiRecentlyWorkspaces)
                {
                    tcManagerPanels.SelectedItem = tiRecentlyWorkspaces;
                }

                if (cdLeftToolsArea.ActualWidth <= 0)
                {
                    cdLeftToolsArea.Width = new GridLength(360, GridUnitType.Pixel);
                }

                var item = lbxHistoryWorkspaces.Items[num - 1] as RecentDirectoryListBoxItem;
                if (item != null && item.DirectoryPath != Globals.PathOfWorkspace)
                {
                    var result = LMessageBox.Show("真的要把工作区切换到以下目录吗？\r\n\r\n　　" + item.DirectoryPath, Globals.AppName,
                         MessageBoxButton.YesNo, MessageBoxImage.Question);
                    if (result == MessageBoxResult.Yes)
                    {
                        ChangeWorkspace(item.DirectoryPath);
                    }
                }
            }
        }

        /// <summary>
        /// 处理与主界面其它区域可能冲突的快捷键。
        /// 这样在主界面其它区仍然可以使用这些快捷键。
        /// 例如，可以在“历史工作区目录”继续使用 Ctrl+Shift+8，作为快捷键。
        /// </summary>
        void mainTabControl_PreviewKeyDown(object sender, KeyEventArgs e)
        {
            KeyStates ksLShift = Keyboard.GetKeyStates(Key.LeftShift);
            KeyStates ksRShift = Keyboard.GetKeyStates(Key.RightShift);
            KeyStates ksLAlt = Keyboard.GetKeyStates(Key.LeftAlt);
            KeyStates ksRAlt = Keyboard.GetKeyStates(Key.RightAlt);
            KeyStates ksLCtrl = Keyboard.GetKeyStates(Key.LeftCtrl);
            KeyStates ksRCtrl = Keyboard.GetKeyStates(Key.RightCtrl);

            bool isCtrl, isShift, isAlt;

            isShift = (ksLShift & KeyStates.Down) > 0 || (ksRShift & KeyStates.Down) > 0;
            isCtrl = (ksLCtrl & KeyStates.Down) > 0 || (ksRCtrl & KeyStates.Down) > 0;
            isAlt = (ksLAlt & KeyStates.Down) > 0 || (ksRAlt & KeyStates.Down) > 0;
            switch (e.Key)
            {
                case Key.D8:
                    {
                        if (isCtrl && !isAlt)
                        {
                            if (this.mainTabControl.SelectedItem != null)
                            {
                                var mdi = this.mainTabControl.SelectedItem as MarkdownEditor;
                                if (mdi != null)
                                {
                                    mdi.EditorBase.SwitchListMark(!isShift);
                                    e.Handled = true;
                                }
                            }
                        }

                        break;
                    }
            }
        }

        /// <summary>
        /// 双击状态栏上的当前工作区路径标签，在 Windows Explorer 中打开该目录。
        /// </summary>
        private void tbxPathOfWorkspace_MouseLeftButtonDown_1(object sender, MouseButtonEventArgs e)
        {
            if (e.ClickCount == 2)
            {
                if (Directory.Exists(Globals.PathOfWorkspace) == false)
                {
                    LMessageBox.Show(string.Format("　　指定的工作区目录[{0}]不存在。这可能是因为程序被安装到Window系统盘的某个目录中，又未获得管理员权限，导致无法创建工作区目录。",
                        Globals.PathOfWorkspace), Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }

                System.Diagnostics.Process.Start("explorer.exe", $"\"{Globals.PathOfWorkspace}\"");
            }
        }

        /// <summary>
        /// 在工作区选定位置创建新 Markdown 文件。
        /// </summary>
        private void btnNew_Click_1(object sender, RoutedEventArgs e)
        {
            NewFile(false);
        }

        /// <summary>
        /// 在工作区选定位置创建新 Markdown 文件。
        /// </summary>
        private void miNewdocument_Click_1(object sender, RoutedEventArgs e)
        {
            NewFile(false);
        }

        /// <summary>
        /// 创建工作区选定目录条目对应的元 Markdonw 文件。
        /// 因为早已改成创建目录时自动添加对应的目录元文件，所以此方法已不必存在。
        /// 保留代码仅备查。
        /// </summary>
        //private void miNewFolderMetaDocument_Click(object sender, RoutedEventArgs e)
        //{
        //    NewFile(false, true);
        //}

        /// <summary>
        /// 保存当前正在编辑的 Markdown 文档（活动编辑器中的文档）。
        /// </summary>
        private void btnSave_Click_1(object sender, RoutedEventArgs e)
        {
            SaveDocment(miFormatBeforeSave.IsChecked);
        }

        /// <summary>
        /// 保存当前正在编辑的 Markdown 文档（活动编辑器中的文档）。
        /// </summary>
        private void miSave_Click_1(object sender, RoutedEventArgs e)
        {
            SaveDocment(miFormatBeforeSave.IsChecked);
        }

        /// <summary>
        /// 打开指定路径的 Markdown 文件（一个或多个）。
        /// </summary>
        /// <param name="fullPathOfFiles">要打开的 Markdown 的文件的路径列表。</param>
        public void OpenDocuments(string[] fullPathOfFiles)
        {
            List<string> files = new List<string>();
            foreach (string s in fullPathOfFiles)
            {
                files.Add(s);
            }
            OpenDocuments(files);
        }

        /// <summary>
        /// 根据指定的路径列表打开这些 Markdown 文件。
        /// </summary>
        /// <param name="fullPathOfFiles">包含 Markdown 文件磁盘路径的列表。</param>
        public void OpenDocuments(List<string> fullPathOfFiles)
        {
            if (fullPathOfFiles == null || fullPathOfFiles.Count <= 0) return;

            StringBuilder errorMsg = new StringBuilder();

            foreach (string s in fullPathOfFiles)
            {
                MarkdownEditor openedFileItem = null;

                foreach (var item in this.mainTabControl.Items)
                {
                    MarkdownEditor eti = item as MarkdownEditor;
                    if (eti != null && eti.FullFilePath == s)
                    {
                        openedFileItem = eti;
                        break;
                    }
                }

                if (openedFileItem != null)
                {
                    this.mainTabControl.SelectedItem = openedFileItem;
                    continue;//已打开的文档，不再重复打开，只是使其成为活动文档。
                }

                MarkdownEditor newEditor = new MarkdownEditor("md_" + (mainTabControl.Items.Count + 1),
                    this.IsExamEnabled, this.IsAutoCompletionEnabled, this.IsEnToChineseDictEnabled,
                    this.IsShowSpaces, this.IsShowEndOfLine, this.IsShowTabs, this.TextAutoWrap, false, this.HighlightingSetting);

                string openResult = newEditor.OpenDocument(s);
                if (openResult == string.Empty)
                {
                    this.mainTabControl.Items.Insert(this.mainTabControl.SelectedIndex + 1, newEditor);
                    this.mainTabControl.SelectedIndex += 1;
                }
                else
                {
                    errorMsg.Append(openResult);
                }

                newEditor.Saved += NewDocument_Saved;
            }

            //如果打开了“演讲者模式”，就在打开文档后刷新大纲视图
            if (Globals.MainWindow.PerspectiveMode == Perspective.EditingAndPresentation)
            {
                var activeEditor = ActivedEditor;
                if (activeEditor != null)
                {
                    activeEditor.EditorBase.UpdateLayout();
                    cmbSearchArea2.SelectedIndex = 0;
                    btnRefreshOutLine_Click(null, null);
                }
            }

            string em = errorMsg.ToString();
            if (em != string.Empty)
            {
                LMessageBox.Show("　　打开文件时出现异常。消息如下：\r\n" + em,
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        /// <summary>
        /// 创建一个新文件后，在工作区管理器中添加对应的条目。
        /// </summary>
        private void NewDocument_Saved(object sender, DocumentSavedEventArgs e)
        {
            var item = FindWorkspaceTreeViewItem(e.FileFullName);
            if (item == null) return;

            item.RefreshFileState();
        }

        /// <summary>
        /// 保存当前打开的所有 Markdown 文档。
        /// </summary>
        private void miSaveAll_Click_1(object sender, RoutedEventArgs e)
        {
            SaveAllDocumentsAndOptions();
        }

        /// <summary>
        /// 退出应用程序。
        /// </summary>
        private void miExit_Click_1(object sender, RoutedEventArgs e)
        {
            //App.Current.Shutdown();//注意，这能使用这个
            this.isForceExit = true;//强行忽略这个字段的值。
            this.Close();
        }

        /// <summary>
        /// 让当前活动编辑器执行“撤销”操作。
        /// </summary>
        private void miUndo_Click_1(object sender, RoutedEventArgs e)
        {
            Undo();
        }

        /// <summary>
        /// 让当前活动编辑器执行“重做”操作。
        /// </summary>
        private void miRedo_Click_1(object sender, RoutedEventArgs e)
        {
            Redo();
        }

        /// <summary>
        /// 从当前活动编辑器剪切选定的文本。
        /// </summary>
        private void miCut_Click_1(object sender, RoutedEventArgs e)
        {
            Cut();
        }

        /// <summary>
        /// 从当前活动编辑器复制选定的文本。
        /// </summary>
        private void miCopy_Click_1(object sender, RoutedEventArgs e)
        {
            Copy();
        }

        /// <summary>
        /// 向当前活动编辑器粘贴数据。
        /// </summary>
        private void miPaste_Click_1(object sender, RoutedEventArgs e)
        {
            Paste();
        }

        /// <summary>
        /// 向当前活动编辑器粘贴代码（会对粘贴的代码自动添加行号）。
        /// </summary>
        private void miPasteCode_Click(object sender, RoutedEventArgs e)
        {
            PasteCode();
        }

        /// <summary>
        /// 向当前活动编辑器粘贴代码（会对粘贴的代码自动添加行号）。
        /// </summary>
        public void PasteCode()
        {
            if (this.mainTabControl.SelectedItem == null) return;

            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            eti.PasteCode();
        }

        /// <summary>
        /// 让当前活动编辑器执行“全选”操作。
        /// </summary>
        private void miSelectAll_Click_1(object sender, RoutedEventArgs e)
        {
            if (this.mainTabControl.SelectedItem == null) return;

            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            eti.EditorBase.SelectAll();
        }

        /// <summary>
        /// 让当前活动编辑器执行“撤销”操作。
        /// </summary>
        private void btnUndo_Click_1(object sender, RoutedEventArgs e)
        {
            Undo();
        }

        /// <summary>
        /// 让当前活动编辑器执行“重做”操作。
        /// </summary>
        private void btnRedo_Click_1(object sender, RoutedEventArgs e)
        {
            Redo();
        }

        /// <summary>
        /// 从当前活动编辑器中剪切选定的文本。
        /// </summary>
        public void Cut()
        {
            if (this.mainTabControl.SelectedItem == null) return;

            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            eti.EditorBase.Cut();
        }

        /// <summary>
        /// 从当前活动编辑器中剪切选定的文本。
        /// </summary>
        private void btnCut_Click_1(object sender, RoutedEventArgs e)
        {
            Cut();
        }

        /// <summary>
        /// 从当前活动编辑器中复制选定的文本。
        /// </summary>
        public void Copy()
        {
            if (this.mainTabControl.SelectedItem == null) return;

            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            eti.EditorBase.Copy();
        }

        /// <summary>
        /// 从当前活动编辑器中复制选定的文本。
        /// </summary>
        private void btnCopy_Click_1(object sender, RoutedEventArgs e)
        {
            Copy();
        }

        /// <summary>
        /// 向当前活动编辑器粘贴文本。
        /// </summary>
        public void Paste()
        {
            if (this.mainTabControl.SelectedItem == null) return;

            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            eti.Paste();
        }

        /// <summary>
        /// 向当前活动编辑器执行粘贴操作。（可能是文本，也可能是图像链接。）
        /// </summary>
        private void btnPaste_Click_1(object sender, RoutedEventArgs e)
        {
            Paste();
        }

        /// <summary>
        /// 向当前活动编辑器粘贴代码。
        /// </summary>
        private void btnPasteCode_Click(object sender, RoutedEventArgs e)
        {
            PasteCode();
        }

        /// <summary>
        /// 刷新各编辑器的字号。
        /// </summary>
        private void RefreshFontSize()
        {
            //foreach (var item in this.mainTabControl.Items)
            //{
            //    MarkdownEditor eti = item as MarkdownEditor;
            //    if (eti == null) continue;

            //    eti.EditorBase.Options.WordWrapIndentation = this.mainTabControl.FontSize * 2;
            //    //直接演示Markdown文档本身时，这个效果在同段换行时非常糟糕，
            //    //容易出现对不齐的现象——尤其使用的字体不是宋体时更易出问题。
            //    //TODO： 暂时去除它。
            //}

            //foreach (var item in this.tcRightToolBar.Items)
            //{
            //    MarkdownEditor eti = item as MarkdownEditor;
            //    if (eti == null) continue;

            //    eti.EditorBase.Options.WordWrapIndentation = this.mainTabControl.FontSize * 2;
            //    //直接演示Markdown文档本身时，这个效果在同段换行时非常糟糕，
            //    //容易出现对不齐的现象——尤其使用的字体不是宋体时更易出问题。
            //    //TODO： 暂时去除它。
            //}

            //foreach (var item in this.tcManagerPanels.Items)
            //{
            //    MarkdownEditor eti = item as MarkdownEditor;
            //    if (eti == null) continue;

            //    eti.EditorBase.Options.WordWrapIndentation = this.mainTabControl.FontSize * 2;
            //    //直接演示Markdown文档本身时，这个效果在同段换行时非常糟糕，
            //    //容易出现对不齐的现象——尤其使用的字体不是宋体时更易出问题。
            //    //TODO： 暂时去除它。
            //}

            //大纲视图也支持字号变化
            tvOutLine.FontSize = this.mainTabControl.FontSize;
        }

        /// <summary>
        /// 放大编辑器的字号。
        /// </summary>
        internal void FontSizeUp()
        {
            if (this.mainTabControl.FontSize <= 98)
            {
                this.mainTabControl.FontSize += 2;//最大字号100。

                //右工具栏中的对照区不能直接对右工具栏变化字号
                foreach (var item in this.tcRightToolBar.Items)
                {
                    MarkdownEditor eti = item as MarkdownEditor;
                    if (eti == null) continue;

                    eti.EditorBase.FontSize = this.mainTabControl.FontSize;
                }

                //左工具栏中的对照区不能直接对左工具栏变化字号
                foreach (var item in this.tcManagerPanels.Items)
                {
                    MarkdownEditor eti = item as MarkdownEditor;
                    if (eti == null) continue;

                    eti.EditorBase.FontSize = this.mainTabControl.FontSize;
                }

                App.ConfigManager.Set("FontSize", this.mainTabControl.FontSize.ToString());
                RefreshFontSize();
            }
        }

        private void btnFontSizeUp_Click_1(object sender, RoutedEventArgs e)
        {
            FontSizeUp();
        }

        /// <summary>
        /// 缩小编辑器的字号。
        /// </summary>
        internal void FontSizeDown()
        {
            if (this.mainTabControl.FontSize >= 8)
            {
                this.mainTabControl.FontSize -= 2;//最小字号6。

                //右工具栏中的对照区不能直接对右工具栏变化字号
                foreach (var item in this.tcRightToolBar.Items)
                {
                    MarkdownEditor eti = item as MarkdownEditor;
                    if (eti == null) continue;

                    eti.EditorBase.FontSize = this.mainTabControl.FontSize;
                }

                //左工具栏中的对照区不能直接对左工具栏变化字号
                foreach (var item in this.tcManagerPanels.Items)
                {
                    MarkdownEditor eti = item as MarkdownEditor;
                    if (eti == null) continue;

                    eti.EditorBase.FontSize = this.mainTabControl.FontSize;
                }

                App.ConfigManager.Set("FontSize", this.mainTabControl.FontSize.ToString());
                RefreshFontSize();
            }
        }

        /// <summary>
        /// 缩小编辑器的字号。
        /// </summary>
        private void btnFontSizeDown_Click_1(object sender, RoutedEventArgs e)
        {
            FontSizeDown();
        }

        /// <summary>
        /// 放大编辑器的字号。
        /// </summary>
        private void miFontSizeUp_Click_1(object sender, RoutedEventArgs e)
        {
            FontSizeUp();
        }

        /// <summary>
        /// 缩小编辑器的字号。
        /// </summary>
        private void miFontSizeDown_Click_1(object sender, RoutedEventArgs e)
        {
            FontSizeDown();
        }

        // <summary>
        /// 重置当前所有编辑器的默认字号。
        /// </summary>
        private void miFontSizeReset_Click_1(object sender, RoutedEventArgs e)
        {
            this.mainTabControl.FontSize = 16;//默认字号。
            RefreshFontSize();
        }

        // <summary>
        /// 给当前活动编辑器中的选定文本两侧加上加粗标志文本。（此操作不能跨行。）
        /// </summary>
        private void WrapWithBold()
        {
            if (this.mainTabControl.SelectedItem == null) return;

            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            if (eti.EditorBase.SelectedText.Contains("\r") || eti.EditorBase.SelectedText.Contains("\n"))
            {
                LMessageBox.Show("  这个操作不允许跨行！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            eti.EditorBase.SwitchBold();
        }

        // <summary>
        /// 给当前活动编辑器中的选定文本两侧加上倾斜标志文本。（此操作不能跨行。）
        /// </summary>
        private void WrapWithItalic()
        {
            if (this.mainTabControl.SelectedItem == null) return;

            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            if (eti.EditorBase.SelectedText.Contains("\r") || eti.EditorBase.SelectedText.Contains("\n"))
            {
                LMessageBox.Show("  这个操作不允许跨行！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            eti.EditorBase.SwitchItalic();
        }

        // <summary>
        /// 给当前活动编辑器中的选定文本两侧加上下划线标志文本。（此操作不能跨行。）
        /// </summary>
        private void WrapWithUTag()
        {
            if (this.mainTabControl.SelectedItem == null) return;

            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            if (eti.EditorBase.SelectedText.Contains("\r") || eti.EditorBase.SelectedText.Contains("\n"))
            {
                LMessageBox.Show("  这个操作不允许跨行！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            if (eti.EditorBase.SelectedText.Length <= 0)
            {
                eti.EditorBase.SelectedText = "<u>" + eti.EditorBase.SelectedText + "</u>";
                eti.EditorBase.Select(eti.EditorBase.SelectionStart + 3, 0);
            }
            else
            {
                eti.EditorBase.SelectedText = "<u>" + eti.EditorBase.SelectedText + "</u>";
            }
        }

        /// <summary>
        /// 给当前活动编辑器中的选定文本两侧加上删除线标志文本。（此操作不能跨行。）
        /// </summary>
        private void WrapWithSTag()
        {
            if (this.mainTabControl.SelectedItem == null) return;

            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            if (eti.EditorBase.SelectedText.Contains("\r") || eti.EditorBase.SelectedText.Contains("\n"))
            {
                LMessageBox.Show("  这个操作不允许跨行！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }
            if (eti.EditorBase.SelectedText.Length <= 0)
            {
                eti.EditorBase.SelectedText = "[=" + eti.EditorBase.SelectedText + "=]";
                eti.EditorBase.Select(eti.EditorBase.SelectionStart + 2, 0);
            }
            else
            {
                eti.EditorBase.SelectedText = "[=" + eti.EditorBase.SelectedText + "=]";
            }
        }

        /// <summary>
        /// 选取当前活动编辑器插入点位置的整行文本。（不能跨行，如果当前选择区是跨行的，只会选中选择区起始位置所在的行。）
        /// </summary>
        private void SelecetLine()
        {
            if (this.mainTabControl.SelectedItem == null) return;

            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            eti.EditorBase.SelectLine();
        }

        /// <summary>
        /// 选取当前活动编辑器插入点位置的整行文本。（不能跨行，如果当前选择区是跨行的，只会选中选择区起始位置所在的行。）
        /// </summary>
        private void miSelectLine_Click_1(object sender, RoutedEventArgs e)
        {
            SelecetLine();
        }

        /// <summary>
        /// 弹出“关于”框，显示本程序相关信息。
        /// </summary>
        private void miAbout_Click_1(object sender, RoutedEventArgs e)
        {
            AboutBox abox = new AboutBox(this) { Owner = App.Current.MainWindow, };
            abox.ShowDialog();
        }

        /// <summary>
        /// 保存当前打开的所有 Markdown 文档。
        /// </summary>
        private void btnSaveAll_Click(object sender, RoutedEventArgs e)
        {
            SaveAllDocumentsAndOptions();
        }

        /// <summary>
        /// 编译、预览当前活动编辑器生成的 Html 页面。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void previewHtml_Click(object sender, RoutedEventArgs e)
        {
            CompileAndPreviewHtml(true);
        }

        /// <summary>
        /// 以全屏方式预览由当前活动编辑器编译生成的 Html 网页。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void previewFullScreenHtml_Click(object sender, RoutedEventArgs e)
        {
            cmbPerspective.SelectedIndex = (int)Perspective.FullScreenPreview;
            CompileAndPreviewHtml(true);
        }

        /// <summary>
        /// 编译当前活动编辑器为 Html 网页文件，并在浏览器中预览。
        /// </summary>
        /// <param name="callSystemDefaultExplorer">是否调用 Windows 默认网页浏览器来预览。默认为 false，表示直接使用 LME 自带的浏览器预览。</param>
        public void CompileAndPreviewHtml(bool callSystemDefaultExplorer = false)
        {
            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            if (eti.IsModified)
            {
                var messageResult = LMessageBox.Show("　　文档需要先保存，要继续吗？",
                    Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Question);
                if (messageResult != MessageBoxResult.Yes) return;

                if (Globals.MainWindow.miFormatBeforeSave.IsChecked)
                {
                    eti.FormatedMarkdownText();
                }

                string result;

                if (File.Exists(eti.FullFilePath))
                {
                    result = eti.SaveDocument();
                }
                else
                {
                    result = SaveNewFile(ActivedEditor);
                }

                if (result != string.Empty) return;
            }

            if (callSystemDefaultExplorer == false)
            {
                eti.CompileAndPreviewHtml();
            }
            else
            {
                eti.CompileAndPreviewHtml();
            }
        }


        /// <summary>
        /// 将当前编辑器内的文本按水平线分割成小文并分别编译，然后再演示。
        /// </summary>
        public void CompileAndPresentateHtml(CustomMarkdownSupport.PresentateHtmlSplitterType presentateHtmlSplitterType, bool onlyPresentateHeaders = false)
        {
            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            if (eti.IsModified)
            {
                var messageResult = LMessageBox.Show("　　文档需要先保存，要继续吗？",
                    Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Question);
                if (messageResult != MessageBoxResult.Yes) return;

                if (Globals.MainWindow.miFormatBeforeSave.IsChecked)
                {
                    eti.FormatedMarkdownText();
                }

                string result;

                if (File.Exists(eti.FullFilePath))
                {
                    result = eti.SaveDocument();
                }
                else
                {
                    result = SaveNewFile(ActivedEditor);
                }

                if (result != string.Empty) return;
            }

            eti.CompileAndPresentateHtml(presentateHtmlSplitterType, onlyPresentateHeaders);
        }

        /// <summary>
        /// 在工作区管理器当前选定项所指向的目录下，创建新 Markdown 文档。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void miNewFile_Click(object sender, RoutedEventArgs e)
        {
            NewFile(false);
        }

        /// <summary>
        /// 在工作区管理器当前选定项所指向的目录下，创建新示例文档。
        /// </summary>
        private void miNewSampleFile_Click(object sender, RoutedEventArgs e)
        {
            NewFile(true);
        }

        /// <summary>
        /// 在工作区管理器当前选定项的位置新建一个 Markdown 文档。
        /// 如果当前项代表目录，则在该目录下面创建新 Markdown 文档；
        /// 如果当前项代表某个 Markdown 文件，则在该 Markdown 文件所在的文件夹下创建新 Markdown 文件。
        /// </summary>
        /// <param name="isSampleFile">是否示例文档。如为 true，则创建的文档带内容。</param>
        /// <param name="isMetaFile">是否目录元文件。</param>
        /// <param name="parentItem">最后两个参数是供“选中一个目录时添加同级Markdown文件”这种特殊情况提供的。</param>
        /// <param name="indexInParentItems">最后两个参数是供“选中一个目录时添加同级Markdown文件”这种特殊情况提供的。</param>
        private void NewFile(bool isSampleFile, bool isMetaFile = false, WorkspaceTreeViewItem parentItem = null, int? indexInParentItems = null)
        {
            WorkspaceTreeViewItem wtvi;
            if (tvWorkDirectory.SelectedItem == null)
            {
                LMessageBox.Show("　　请先在主界面左侧工作区管理器窗口中选定一个目录来创建文件。" +
                    "如果看不到工作区浏览窗口，请按F12键显示左工具栏。",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Information);
                return;
            }
            else
            {
                wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            }

            string directoryPath;
            WorkspaceTreeViewItem destWtvi;

            if (wtvi.IsMarkdownFilePath)
            {
                directoryPath = wtvi.ParentDirectory;
                destWtvi = wtvi.ParentWorkspaceTreeViewItem;
            }
            else
            {
                directoryPath = wtvi.FullPath;
                destWtvi = wtvi;
            }

            if (Directory.Exists(directoryPath) == false)
            {
                var lastIndex = directoryPath.LastIndexOf("\\");
                if (lastIndex < 0)
                {
                    LMessageBox.Show("　　只能在目录下新建文件。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }

                var parentDirectory = directoryPath.Substring(0, lastIndex);
                if (Directory.Exists(parentDirectory) == false)
                {
                    LMessageBox.Show("　　只能在目录下新建文件。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }

                directoryPath = parentDirectory;
            }

            if (directoryPath.EndsWith("~") || directoryPath.EndsWith("~\\"))
            {
                LMessageBox.Show("　　以波形符结尾的目录是程序自动添加的资源目录，不能在其中再建立MarkDown文件。",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            if (directoryPath.EndsWith("\\") == false) directoryPath += "\\";

            string newShortFileName;

            if (isMetaFile)
            {
                newShortFileName = "_" + new DirectoryInfo(directoryPath).Name + ".md";
            }
            else
            {
                newShortFileName = InputBox.Show(Globals.AppName, "请输入新文件名（不能以“_”开头）：", "", true,
                   "说明：\r\n　　⑴不需要输入后缀名；\r\n　　⑵请尽可能设定有意义的文件名。\r\n　　⑶不能以下划线开头。\r\n　　⑷尽量以字母开头。\r\n　　因为此文件名很可能将来被其它文件引用。所以，如非必要，创建之后请勿随意更改文件名。");
            }

            if (string.IsNullOrWhiteSpace(newShortFileName)) return;//用户放弃新建文件。

            var newShortCommentName = newShortFileName;
            newShortFileName = ChinesePinYin.ToChinesePinYinText(newShortCommentName);

            if (newShortFileName.ToLower().EndsWith(".md") == false) newShortFileName += ".md";

            if (string.IsNullOrEmpty(newShortFileName))
            {
                LMessageBox.Show("　　文件名称不能为空！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }

            var newFilePath = directoryPath + newShortFileName;
            if (File.Exists(newFilePath))
            {
                LMessageBox.Show("　　已存在同名文件，无法完成操作！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }

            try
            {
                string docTitle = newShortFileName;
                using (StreamWriter sw = File.CreateText(newFilePath))
                {
                    if (isSampleFile)
                    {
                        sw.Write(Properties.Resources.example);
                    }
                    else
                    {
                        docTitle = newShortCommentName.EndsWith(".md") ? newShortCommentName.Substring(0, newShortCommentName.Length - 3) : newShortCommentName;

                        Regex headerNumberRegex = new Regex(@"^[lL]\d{7,}[ 　-]");
                        var matchHeaderNumber = headerNumberRegex.Match(docTitle);
                        if (matchHeaderNumber.Success)
                        {
                            docTitle = docTitle.Substring(matchHeaderNumber.Length);
                        }

                        sw.Write($"\r\n%{/*FormatDocumentTitle(*/docTitle/*)*/}\r\n\r\n；{DateTime.Now.ToString()}\r\n\r\n");
                    }
                }

                WorkspaceTreeViewItem newtvi = new WorkspaceTreeViewItem(newFilePath, Globals.MainWindow);
                if (isMetaFile == false)
                {
                    //2017年7月21日，现在不需要再排序了，让用户决定添加在哪里
                    //如果是添加下级，直接添加到下级子节点的最后一个位置。
                    //如果是添加同级，直接添加到当前节点的后面。
                    #region 废弃代码
                    //List<WorkspaceTreeViewItem> tmpItems = new List<WorkspaceTreeViewItem>();
                    //int baseIndex = 0;
                    //foreach (var item in destWtvi.Items)
                    //{
                    //    var wi = item as WorkspaceTreeViewItem;
                    //    if (wi != null)
                    //    {
                    //        if (wi.ShortName.EndsWith("~"))//波型符结尾的总是资源文件夹。
                    //        {
                    //            baseIndex++;
                    //            continue;
                    //        }
                    //        tmpItems.Add(wi);
                    //    }
                    //}
                    //tmpItems.Add(newtvi);
                    //tmpItems.Sort(new WorkspaceTreeViewItemCompare());
                    //var index = tmpItems.IndexOf(newtvi) + baseIndex;
                    //destWtvi.Items.Insert(index, newtvi);
                    #endregion

                    //这种情况下，如果选中的是目录，就添加到目录节点的子节点中最后一个位置；
                    //如果选中的是 Markdown 文件，就添加到此文件节点的后一个位置。
                    if (parentItem != null && indexInParentItems.HasValue)
                    {
                        //这是供“选中一个目录时添加同级Markdown文件”这种特殊情况提供的。
                        parentItem.Items.Insert(indexInParentItems.Value, newtvi);
                    }
                    else
                    {
                        if (wtvi.IsMarkdownFilePath)
                        {
                            var index = destWtvi.Items.IndexOf(wtvi);
                            destWtvi.Items.Insert(index + 1, newtvi);
                        }
                        else
                        {
                            //选中目录
                            destWtvi.Items.Add(newtvi);
                        }
                    }

                    newtvi.IsSelected = true;
                    WorkspaceManager.SaveWorkspaceTreeviewToXml();
                }//元文件不显示。

                OpenDocuments(new string[] { newFilePath });

                //TODO: 仍然无法实现使编辑器获取焦点
                FocusActiveEditor();
            }
            catch (Exception ex)
            {
                LMessageBox.Show("　　新建文件失败！错误消息：\r\n" + ex.Message + "\r\n" + ex.StackTrace,
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        /// <summary>
        /// 将字符串开头的数字去除。例如：1－2－3－xxx，格式化后只保留xxx。但如果只剩下空格，就返回原始值。
        /// </summary>
        /// <param name="src">源文本。</param>
        /// <returns>返回去除了数字前缀的文件短名。</returns>
        public static string FormatDocumentTitle(string src)
        {
            if (string.IsNullOrWhiteSpace(src)) return string.Empty;

            if (src.ToLower().EndsWith(".html"))
            {
                src = src.Substring(0, src.Length - 5);
            }

            if (src.ToLower().EndsWith(".md"))
            {
                src = src.Substring(0, src.Length - 3);
            }

            Regex regex = new Regex(@"^[ 　\t0123456789１２３４５６７８９０\-_]*");
            var match = regex.Match(src);
            string result = null;
            if (match.Success)
                result = src.Substring(match.Length);

            if (string.IsNullOrWhiteSpace(result))
            {
                return src;
            }

            return result;
        }

        /// <summary>
        /// 删除工作区管理器中某个文件或目录。
        /// </summary>
        private void miDeleteFile_Click(object sender, RoutedEventArgs e)
        {
            if (tvWorkDirectory.SelectedItem == null)
            {
                LMessageBox.Show("　　请先选中要删除的文件。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            WorkspaceTreeViewItem wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;

            if (wtvi.FullPath.EndsWith("~") || wtvi.FullPath.EndsWith("~\\") || wtvi.FullPath == Globals.PathOfWorkspace)
            {
                LMessageBox.Show("　　这个文件或目录是程序自动生成的，不能直接删除。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            try
            {
                if (Directory.Exists(wtvi.FullPath))
                {
                    var result = LMessageBox.Show("　　删除目录会删除其中的所有文件和子目录。真的要删除此目录吗？", Globals.AppName,
                        MessageBoxButton.YesNo, MessageBoxImage.Warning);
                    if (result != MessageBoxResult.Yes) return;

                    Directory.Delete(wtvi.FullPath, true);
                    //workspaceManager.Refresh(null);
                    var parent = wtvi.Parent as WorkspaceTreeViewItem;
                    if (parent != null && parent.Items.Contains(wtvi))
                    {
                        parent.Items.Remove(wtvi);
                    }
                    return;
                }

                if (File.Exists(wtvi.FullPath))
                {
                    if (wtvi.IsImageFileExist)
                    {
                        ImagePreview.Source = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/App.png"));
                        tbImageTitle.Text = "";
                        tbImageTitle.Visibility = Visibility.Collapsed;

                        File.Delete(wtvi.FullPath);

                        var parentItem = (wtvi.Parent as TreeViewItem);
                        parentItem.Items.Remove(wtvi);
                        parentItem.IsSelected = true;
                    }
                    else if (wtvi.IsMarkdownFilePath)
                    {
                        try
                        {
                            DirectoryInfo resourceDirectoryInfo = new DirectoryInfo(wtvi.ResourceDirectoryFullPath);

                            MarkdownEditor openedEditor = null;
                            foreach (var item in this.mainTabControl.Items)
                            {
                                var editor = item as MarkdownEditor;
                                if (editor == null) continue;

                                if (editor.FullFilePath.ToLower() == wtvi.FullPath.ToLower())
                                {
                                    openedEditor = editor;
                                    break;
                                }
                            }

                            if (openedEditor != null)
                            {
                                this.mainTabControl.Items.Remove(openedEditor);
                            }

                            File.Delete(wtvi.FullPath);

                            if (Directory.Exists(resourceDirectoryInfo.FullName))
                            {
                                Directory.Delete(resourceDirectoryInfo.FullName, true);
                            }

                            var parentItem = (wtvi.Parent as TreeViewItem);
                            parentItem.Items.Remove(wtvi);
                            parentItem.IsSelected = true;

                            WorkspaceManager.SaveWorkspaceTreeviewToXml();
                        }
                        catch (Exception ex)
                        {
                            LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        /// <summary>
        /// 将当前 Markdown 文件、Image 文件、目录元文件的链接插入到当前活动编辑器的插入点位置处。
        /// </summary>
        private void miInsertLinkToCurrentDocument_Click(object sender, RoutedEventArgs e)
        {
            WorkspaceTreeViewItem wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (wtvi == null)
            {
                LMessageBox.Show("请先在工作区目录中选择一个文件作为要链接的目标文件！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            var linkSourceFullPath = wtvi.FullPath;

            if (wtvi.IsMarkdownFilePath == false)
            {
                if (wtvi.IsImageFileExist)
                {
                    wtvi.InsertImageTagToDocument(wtvi.FullPath, true);
                    return;
                }
                else if (wtvi.IsSoundFileExist)
                {
                    wtvi.InsertSoundTagToDocument(wtvi.FullPath, true);
                    return;
                }
                else if (wtvi.IsVedioFileExist)
                {
                    wtvi.InsertVedioTagToDocument(wtvi.FullPath, true);
                    return;
                }
                else
                {
                    if (wtvi.FullPath.ToLower() == Globals.PathOfWorkspace.ToLower())
                    {
                        LMessageBox.Show("不能将工作区根目录作为链接插入到当前文档中！",
                            Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                        return;
                    }
                    else
                    {
                        var di = new DirectoryInfo(wtvi.FullPath);
                        var directoryMdFileFullName = (di.FullName.EndsWith("\\") ? di.FullName : (di.FullName + "\\")) + "_" + di.Name + ".md";
                        linkSourceFullPath = directoryMdFileFullName;
                        CreateDirectoryMetaMdFile(di, directoryMdFileFullName);

                        InsertLinkToActiveDocument(wtvi, linkSourceFullPath);
                        return;
                    }
                }
            }

            InsertLinkToActiveDocument(wtvi, linkSourceFullPath);
        }

        /// <summary>
        /// 取目录对应元文件路径。
        /// </summary>
        /// <param name="directoryFullPath">目录完整路径。</param>
        /// <returns></returns>
        public string GetMetaFilePathOfDirectory(string directoryFullPath)
        {
            if (Directory.Exists(directoryFullPath) == false) return "";
            var metaFileName = directoryFullPath;
            if (metaFileName.EndsWith("\\") == false) metaFileName += "\\";
            DirectoryInfo di = new DirectoryInfo(directoryFullPath);

            metaFileName = metaFileName + "_" + di.Name + ".md";
            return metaFileName;
        }

        /// <summary>
        /// 取目录元文件的标题。
        /// </summary>
        /// <param name="directoryFullPath"></param>
        /// <returns></returns>
        public string GetMetaFileTitleOfDirectory(string directoryFullPath)
        {
            return GetTitleOfMdFile(GetMetaFilePathOfDirectory(directoryFullPath));
        }

        /// <summary>
        /// 在当前活动编辑器的插入点位置添加对工作区管理器中当前选定的 Markdown 文件的链接。
        /// </summary>
        /// <param name="wtvi">工作区管理器中表示某个 Markdown 文件或某个目录的条目。（如果是目录，添加的应是其对应的元文件。</param>
        /// <param name="linkSourceFullPath">源文件路径，如果是目录，应传入目录元文件的路径。</param>
        private void InsertLinkToActiveDocument(WorkspaceTreeViewItem wtvi, string linkSourceFullPath)
        {
            //将当前选定文档作为一个链接插入到正在编辑的文档中。
            var editor = Globals.MainWindow.ActivedEditor;
            if (editor == null)
            {
                LMessageBox.Show("当前没有打开任何文档，无法添加对此文件的引用！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            var destFilePath = editor.FullFilePath;
            var srcPath = linkSourceFullPath;//wtvi.FullPath.ToLower();

            string shortName;

            if (File.Exists(wtvi.FullPath))
            {
                FileInfo fi = new FileInfo(wtvi.FullPath);
                shortName = fi.Name.Substring(0, fi.Name.Length - fi.Extension.Length);
            }
            else if (Directory.Exists(wtvi.FullPath))
            {
                DirectoryInfo di = new DirectoryInfo(wtvi.FullPath);

                //判断目录元文件是否存在
                var metaFileName = wtvi.FullPath;
                if (metaFileName.EndsWith("\\") == false) metaFileName += "\\";

                //目录元文件的短名实际上是以"_"开头的，但使用"_"会和Markdown 基本格式语法中的“倾斜”效果冲突。
                //于是自动添加的链接中就以"~"代替"_"，在编译时再转换回去。——这样视觉效果要好得多。
                metaFileName = metaFileName + "_" + di.Name + ".md";

                if (File.Exists(metaFileName))
                {
                    shortName = GetTitleOfMdFile(metaFileName);
                }
                else shortName = di.Name;
            }
            else shortName = "";

            var title = MainWindow.GetTitleOfMdFile(wtvi.FullPath);
            if (string.IsNullOrWhiteSpace(title) == false)
            {
                shortName = title;
            }

            editor.EditorBase.SelectedText = BuildLinkText(srcPath, destFilePath, ref shortName);
            var destSel = editor.EditorBase.SelectionStart + 1;
            if (destSel < editor.EditorBase.Document.TextLength)
            {
                editor.EditorBase.Select(destSel, Math.Max(FormatDocumentTitle(shortName).Length, 0));
            }
        }

        /// <summary>
        /// 按照工作区管理器中选定的 Markdown 文件条目查找所有对此文件的链接。
        /// </summary>
        private void miFindAllLinkToThisFile_Click(object sender, RoutedEventArgs e)
        {
            WorkspaceTreeViewItem wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (wtvi == null)
            {
                LMessageBox.Show("请先在工作区目录中选择一个文件作为要查找的文件！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            if (wtvi.IsMarkdownFilePath == false && wtvi.IsImageFileExist == false)
            {
                LMessageBox.Show("　　只有Markdown文件、图片资源文件才能链接（且需要编译为Html文档才有效）！",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            tvFindAndReplace.Items.Clear();

            FileInfo fi = new FileInfo(wtvi.FullPath);
            var shortName = fi.Name.Substring(0, fi.Name.Length - fi.Extension.Length);
            var item = new FindDocumentTreeViewItem(wtvi.FullPath, wtvi.ShortName, $"{wtvi.ShortName} 被引用于：");

            FindAllLinksToThisFile(item, fi.FullName, Globals.PathOfWorkspace, shortName);

            if (item.HasItems)
            {
                tvFindAndReplace.Items.Add(item);
                item.IsExpanded = true;
            }
            else
            {
                tvFindAndReplace.Items.Add(new TreeViewItem() { Header = "<没有找到任何引用...>" });
            }

            tcRightToolBar.SelectedItem = tiFindResult;
        }

        /// <summary>
        /// 查找所有对此文件的引用。
        /// </summary>
        /// <param name="item">将查找结果放到此项目下级。</param>
        /// <param name="srcPath">查找的路径。</param>
        /// <param name="destDirectory">目的目录路径。</param>
        /// <param name="shortName">短名。</param>
        private void FindAllLinksToThisFile(FindDocumentTreeViewItem item, string srcPath, string destDirectory, string shortName)
        {
            if (File.Exists(srcPath) == false) return;

            if (Directory.Exists(destDirectory) == false) return;

            var directoryInfo = new DirectoryInfo(destDirectory);
            var files = directoryInfo.GetFiles();

            foreach (var file in files)
            {
                if (file.Extension.ToLower() != ".md" &&
                   WorkspaceTreeViewItem.IsValidateImageFilePath(file.FullName) == false) continue;

                var linkText = BuildLinkText(srcPath, file.FullName, ref shortName);
                var mIndex = linkText.IndexOf("](");
                if (linkText.StartsWith("[") && linkText.EndsWith(")") &&
                    (mIndex >= 1 && mIndex < linkText.Length - 2))
                {
                    linkText = linkText.Substring(mIndex + 1, linkText.Length - mIndex - 1);
                }
                //BuildLinkText()其实只针对文字链接，但稍加修改也可以用于图像文本链接，
                //因为图像文件链接只在头部多个！号而已。

                var edit = GetOpenedEditor(file.FullName);
                string[] lines;
                if (edit != null)
                {
                    //打开的文件，以正在编辑的内容为准
                    lines = edit.EditorBase.Text.Replace("\r", "").Split(new char[] { '\n' }, StringSplitOptions.None);
                }
                else
                {
                    //没打开的文件，以磁盘数据为准
                    lines = File.ReadAllLines(file.FullName);
                }

                var lineNumber = 0;
                foreach (var line in lines)
                {
                    lineNumber++;
                    Regex regex = new Regex(@"!?\[.*\](.*)");
                    var match = regex.Match(line);
                    if (match.Success)
                    {
                        var destLine = match.Value.ToLower();
                        var srcLine = linkText.ToLower();

                        bool isLinked = destLine.Contains(srcLine);
                        if (isLinked == false)
                        {
                            if (destLine.Contains("](./"))
                            {
                                if (srcLine.StartsWith("(./") == false)
                                {
                                    srcLine = "(./" + srcLine.Substring(1);
                                    isLinked = destLine.Contains(srcLine);
                                }
                            }
                            else
                            {
                                if (srcLine.StartsWith("(./"))
                                {
                                    srcLine = "(" + srcLine.Substring(3);
                                    isLinked = destLine.Contains(srcLine);
                                }
                            }
                        }

                        if (isLinked)
                        {
                            item.Items.Add(new FindLineTreeViewItem(file.FullName, file.Name, lineNumber, match.Index, match.Length,
                                $"文件：{file.Name}，第 {lineNumber} 行", null, FindLineTreeViewItem.ItemType.Normal));
                        }
                    }
                }
            }

            var subDirectories = directoryInfo.GetDirectories();
            foreach (var subDirectory in subDirectories)
            {
                if (subDirectory.FullName.EndsWith("~")) continue;

                FindAllLinksToThisFile(item, srcPath, subDirectory.FullName, subDirectory.Name);
            }
        }

        /// <summary>
        /// 取引用链接文本。
        /// </summary>
        /// <param name="resourceFullPathName">是指将被引用的资源的文件路径</param>
        /// <param name="mdFileFullPathName">resourceFullPathName。</param>
        /// <param name="shortName">资源短名，通常是文件名。</param>
        /// <returns>生成的用于插入到<para>mdFileFullPathName</para>指向的Md文件中的链接的文本。</returns>
        private string BuildLinkText(string resourceFullPathName, string mdFileFullPathName, ref string shortName)
        {
            //if (resourceFullPathName != null)
            //{
            //    resourceFullPathName = resourceFullPathName.ToLower();
            //}

            //if (mdFileFullPathName != null)
            //{
            //    mdFileFullPathName = mdFileFullPathName.ToLower();
            //}

            if (resourceFullPathName.EndsWith(".md"))
            {
                resourceFullPathName = resourceFullPathName.Substring(0, resourceFullPathName.Length - 3) + ".html";
            }

            var workspacePath = Globals.PathOfWorkspace.ToLower();

            //思路：先找出共同的、最接近的祖先级目录
            var separator = new char[] { '\\', '/' };
            string[] src = resourceFullPathName.Split(separator, StringSplitOptions.RemoveEmptyEntries);
            string[] dest = mdFileFullPathName.Split(separator, StringSplitOptions.RemoveEmptyEntries);

            var minIndex = Math.Min(src.Length, dest.Length) - 1;

            if (minIndex < 0) return "";

            int aIndex = -1;
            for (int i = 0; i <= minIndex; i++)
            {
                if (src[i].ToLower() != dest[i].ToLower())
                {
                    aIndex = i;
                    break;
                }
            }

            //生成相对路径的前半部分，类似（../../）这样子。
            string[] file = mdFileFullPathName.Split(separator, StringSplitOptions.RemoveEmptyEntries);
            string header;
            if (resourceFullPathName == mdFileFullPathName || aIndex < 0)//同一文件内部引用
            {
                header = "";
            }
            else
            {
                header = FindLineTreeViewItem.BuildHtmlRefText(file.Length - aIndex - 1);

                for (int i = aIndex; i < src.Length; i++)
                {
                    header += src[i];
                    header += "/";
                }

                if (header.EndsWith(".md"))
                {
                    header = header.Substring(0, header.Length - 3) + ".html";
                }

                if (header.EndsWith("/"))
                {
                    header = header.Substring(0, header.Length - 1);
                }
            }

            if (shortName.EndsWith(".md"))
            {
                shortName = shortName.Substring(0, shortName.Length - 3);
            }

            var linkText = "[" + FormatDocumentTitle(shortName) + "](" + header + ")";
            return linkText;
        }

        /// <summary>
        /// 在工作区管理器中创建子目录。
        /// </summary>
        private void miNewDirectory_Click(object sender, RoutedEventArgs e)
        {
            if (tvWorkDirectory.SelectedItem == null)
            {
                LMessageBox.Show("　　请先选中要在哪个目录下新建子目录。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            WorkspaceTreeViewItem wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (Directory.Exists(wtvi.FullPath) == false)
            {
                LMessageBox.Show("　　只能在目录下新建子目录。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            DirectoryInfo di = new DirectoryInfo(wtvi.FullPath);
            if (di.Name.EndsWith("~"))
            {
                LMessageBox.Show("　　以波形符结尾的目录是程序自动管理的资源目录，不允许在其下新建子目录。",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            var newShortDirectoryName = InputBox.Show(Globals.AppName, "请输入新目录名（不能以“_”开头，尽量以字母开头）：", "", true);

            var newShortCommentName = newShortDirectoryName;

            newShortDirectoryName = ChinesePinYin.ToChinesePinYinText(newShortCommentName);

            if (string.IsNullOrEmpty(newShortDirectoryName))
            {
                LMessageBox.Show("　　目录名称不能为空！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }

            try
            {
                var newFullPath = (wtvi.FullPath.EndsWith("\\") ? wtvi.FullPath : (wtvi.FullPath + "\\")) + newShortDirectoryName;
                if (Directory.Exists(newFullPath) == false)
                {
                    Directory.CreateDirectory(newFullPath);
                    var destDirectoryInfo = new DirectoryInfo(newFullPath);

                    //自动创建目录元文件，不必再双击了。

                    Regex headerNumberRegex = new Regex(@"^[lL]\d{7,}[ 　-]");
                    var matchHeaderNumber = headerNumberRegex.Match(newShortCommentName);
                    if (matchHeaderNumber.Success)
                    {
                        newShortCommentName = newShortCommentName.Substring(matchHeaderNumber.Length);
                    }

                    using (var stream = File.CreateText(destDirectoryInfo.FullName + "\\" + "_" + destDirectoryInfo.Name + ".md"))
                    {
                        stream.Write($"\r\n%{/*FormatDocumentTitle(*/newShortCommentName/*)*/}\r\n\r\n；{DateTime.Now.ToString()}\r\n\r\n");
                    }
                }
                else
                {
                    LMessageBox.Show("　　已存在同名目录，操作无法完成。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Error);
                    return;
                }

                var newItem = new WorkspaceTreeViewItem(newFullPath, Globals.MainWindow);
                wtvi.Items.Add(newItem);

                //2017年7月21日，现在由用户决定插入位置，不再自动排序。
                //插入同级节点时，直接添加到当前节点下一个位置；插入下级节点，直接添加到下级的最后一个位置。
                #region 废弃代码
                ////解决索引问题，判断在哪里插入。
                //List<WorkspaceTreeViewItem> tmpItems = new List<WorkspaceTreeViewItem>();
                //int baseIndex = 0;
                //foreach (var item in wtvi.Items)
                //{
                //    var wi = item as WorkspaceTreeViewItem;
                //    if (wi != null)
                //    {
                //        if (wi.ShortName.EndsWith("~"))//波型符结尾的总是资源文件夹。
                //        {
                //            baseIndex++;
                //            continue;
                //        }
                //        tmpItems.Add(wi);
                //    }
                //}
                //tmpItems.Add(newItem);
                //tmpItems.Sort(new WorkspaceTreeViewItemCompare());
                //var index = tmpItems.IndexOf(newItem) + baseIndex;
                //wtvi.Items.Insert(index, newItem); 
                #endregion

                newItem.IsSelected = true;
                workspaceManager.SaveWorkspaceTreeviewToXml();
            }
            catch (Exception ex)
            {
                LMessageBox.Show("　　创建目录失败！错误消息：\r\n" + ex.Message + "\r\n" + ex.StackTrace,
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Error);

            }
        }

        /// <summary>
        /// 切换活动文档时自动对焦，以便直接输入文本。
        /// </summary>
        private void mainTabControl_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
        {
            if (mainTabControl.SelectedIndex < 0)
            {
                cmbSearchArea2.SelectedIndex = 2;
                cmbSearchArea.SelectedIndex = 2;
            }

            RefreshTextInfos();

            //所有文档都关闭后，自动退出全屏状态
            if (this.mainTabControl.Items.Count <= 0)
            {
                if (cmbPerspective.SelectedIndex == (int)Perspective.FullScreenPreview ||
                    cmbPerspective.SelectedIndex == (int)Perspective.FullScreenEditing)
                {
                    cmbPerspective.SelectedIndex = (int)Perspective.Normal;
                }
            }

            var activeEdit = this.ActivedEditor;
            if (activeEdit != null)
            {
                var destWorkspaceTreeViewItem = FindWorkspaceTreeViewItem(activeEdit.FullFilePath);
                if (destWorkspaceTreeViewItem != null)
                {
                    destWorkspaceTreeViewItem.IsSelected = true;
                    var parentItem = destWorkspaceTreeViewItem.ParentWorkspaceTreeViewItem;
                    while (parentItem != null)
                    {
                        parentItem.IsExpanded = true;
                        parentItem = parentItem.ParentWorkspaceTreeViewItem;
                    }
                }

                activeEdit.EditorBase.Focus();
            }

            RefreshTextAutoWrapToStatusBar();
        }

        /// <summary>
        /// 选择主题来编译 Html 文件。
        /// </summary>
        private void cmbColor_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (App.WorkspaceConfigManager == null || cmbColor.SelectedItem == null) return;

            var colorName = (cmbColor.SelectedItem as ComboBoxItem).Tag.ToString();
            //App.WorkspaceConfigManager.Set("color", colorName);
            //不能在这里设置，否则会导致循环设置、死锁

            if (colorName == "light")
            {
                imagePreviewOutBorder.Background = Brushes.White;
                tbImageTitle.Foreground = Brushes.Black;
            }
            else if (colorName == "dark")
            {
                imagePreviewOutBorder.Background = new SolidColorBrush(Color.FromRgb(0x3D, 0x3D, 0x3D));
                tbImageTitle.Foreground = Brushes.White;
            }
            else
            {
                imagePreviewOutBorder.Background = Brushes.Transparent;
                tbImageTitle.Foreground = Brushes.Black;
            }
        }

        /// <summary>
        /// 在工作区管理器中重命名文件或文件夹。
        /// </summary>
        private void miRename_Click(object sender, RoutedEventArgs e)
        {
            if (tvWorkDirectory.SelectedItem == null)
            {
                LMessageBox.Show("　　请先选中要重命名的项目。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            WorkspaceTreeViewItem wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;

            var result = LMessageBox.Show("　　重命名文件（或目录）会导致所有对此文件（或目录）的引用（链接）失效。\r\n　　" +
                "> 对与此文件相关的资源文件的引用也会失效。\r\n\r\n" +
                "　　建议先使用“查找引用”功能看看此文件（或此目录）是否被引用过。\r\n\r\n" +
                "　　要自动查找一下可能存在的引用么？（要注意可能出现同名文件被引用！）",
                Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Warning, "NPA_WarningBeforeRename");
            //NPA_ means No Prompt Again.

            if (result == MessageBoxResult.Yes)
            {
                cmbFindText.Text = wtvi.ShortName;
                cmbSearchArea.SelectedIndex = 2;
                FindText(tvFindAndReplace);
                return;
            }

            if (wtvi.IsDirectoryExists)
            {
                if (wtvi.FullPath == Globals.PathOfWorkspace)
                {
                    LMessageBox.Show("　　不能重命名工作区目录！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }

                if (wtvi.FullPath.EndsWith("~") || wtvi.FullPath.EndsWith("~\\"))
                {
                    LMessageBox.Show("　　这是程序预定的资源目录名，不可修改！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }

                var newShortName = InputBox.Show(Globals.AppName, "请输入新目录名称（不能以“_”开头，尽量以字母开头）：", wtvi.ShortName, true);
                if (string.IsNullOrEmpty(newShortName))
                {
                    LMessageBox.Show("　　指定的目录名不合法。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }

                try
                {
                    #region 在重命名一个目录之前，先将该目录对应的元文件、元文件资源文件夹、编译后的html元文件重命名。
                    var oldFullPath = wtvi.FullPath;
                    if (oldFullPath.EndsWith("\\") == false) oldFullPath += "\\";

                    var newFullPath = wtvi.FullPath;
                    if (newFullPath.EndsWith("\\")) newFullPath = newFullPath.Substring(0, newFullPath.Length - 1);

                    string oldShortName = "";
                    var lastindex = newFullPath.LastIndexOf("\\");
                    if (lastindex >= 0)
                    {
                        oldShortName = newFullPath.Substring(lastindex + 1);
                        newFullPath = newFullPath.Substring(0, lastindex);
                    }

                    newFullPath = newFullPath + "\\" + newShortName + "\\";
                    if (Directory.Exists(newFullPath))
                    {
                        LMessageBox.Show("　　已存在指定名称的目录，操作无法完成。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                        return;
                    }

                    var oldMetaFileShortName = "_" + oldShortName + ".md";
                    var oldMetaFileFullPath = oldFullPath + oldMetaFileShortName;
                    var oldMetaFileResourceDirectoryPath = oldFullPath + "_" + oldShortName + "~\\";
                    var oldMetaHtmlFileFullPath = oldFullPath + "_" + oldShortName + ".html";

                    if (File.Exists(oldMetaFileFullPath))
                        File.Move(oldMetaFileFullPath, oldFullPath + "_" + newShortName + ".md");

                    if (Directory.Exists(oldMetaFileResourceDirectoryPath))
                        Directory.Move(oldMetaFileResourceDirectoryPath, oldFullPath + "_" + newShortName + "~");

                    if (File.Exists(oldMetaHtmlFileFullPath))
                        File.Move(oldMetaHtmlFileFullPath, oldFullPath + "_" + newShortName + ".html");

                    #endregion

                    //然后再命名文件夹本身。
                    Directory.Move(wtvi.FullPath, newFullPath);
                    wtvi.FullPath = newFullPath;

                    //重命名打开的元文件。
                    foreach (var ue in this.mainTabControl.Items)
                    {
                        MarkdownEditor editor = ue as MarkdownEditor;
                        if (editor == null || editor.FullFilePath == null) continue;

                        if (editor.FullFilePath.ToLower().StartsWith(oldFullPath.ToLower()))
                        {
                            var shortName = editor.ShortFileName;
                            if (shortName == null) continue;
                            if (shortName.StartsWith("_"))
                            {
                                //元文件，要特别处理
                                if (oldFullPath.EndsWith("\\")) oldFullPath = oldFullPath.Substring(0, oldFullPath.Length - 1);
                                var lastI = oldFullPath.LastIndexOf("\\");
                                if (lastI >= 0)
                                {
                                    var s = oldFullPath.Substring(lastI + 1);
                                    if (("_" + s + ".md").ToLower() == shortName.ToLower())
                                    {
                                        editor.FullFilePath = newFullPath + "_" + newShortName + ".md";
                                        continue;
                                    }
                                }
                            }

                            editor.FullFilePath = newFullPath + editor.FullFilePath.Substring(oldFullPath.Length);
                        }
                    }

                    WorkspaceManager.SaveWorkspaceTreeviewToXml();
                }
                catch (Exception ex)
                {
                    LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }
            }
            else if (wtvi.IsImageFileExist)
            {
                var newShortCommentName = InputBox.Show(Globals.AppName, "请输入新图像文件名（不能以“_”开头，尽量以字母开头）：", wtvi.ShortName, true, "注意：不要保留后缀名。");
                if (string.IsNullOrWhiteSpace(newShortCommentName)) return;

                if (newShortCommentName.StartsWith("."))
                {
                    LMessageBox.Show("　　指定的文件名不能为空，也不能以句号开头。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }

                var oldFileInfo = new FileInfo(wtvi.FullPath);
                var oldExtension = oldFileInfo.Extension;

                var newShortName = ChinesePinYin.ToChinesePinYinText(newShortCommentName) + oldExtension;

                var newFullPath = oldFileInfo.DirectoryName + "\\" + newShortName;

                if (File.Exists(newFullPath))
                {
                    LMessageBox.Show("　　已存在指定名称的文件，无法完成操作。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }

                try
                {
                    File.Move(wtvi.FullPath, newFullPath);
                    wtvi.FullPath = newFullPath;

                    //写注释文件行
                    var commentTextFilePath = oldFileInfo.DirectoryName + "\\~.txt";
                    if (File.Exists(commentTextFilePath))
                    {
                        using (var stream = File.AppendText(commentTextFilePath))
                        {
                            stream.WriteLine($"{newShortName}|{newShortCommentName}{oldExtension}");
                        }
                    }
                    else
                    {
                        using (var stream = File.CreateText(commentTextFilePath))
                        {
                            stream.WriteLine($"{newShortName}|{newShortCommentName}{oldExtension}");
                        }
                    }
                    WorkspaceManager.SaveWorkspaceTreeviewToXml();
                }
                catch (Exception ex)
                {
                    LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }
            }
            else if (wtvi.IsFileExists)
            {
                var newShortName = InputBox.Show(Globals.AppName, "请输入新文件名（不能以“_”开头，尽量以字母开头）：", wtvi.ShortName, true);
                if (string.IsNullOrWhiteSpace(newShortName)) return;

                if (newShortName.ToLower() == ".md")
                {
                    LMessageBox.Show("　　指定的文件名不能为空。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }

                if (newShortName.ToLower().EndsWith(".md"))
                {
                    newShortName = newShortName.Substring(0, newShortName.Length - 3);
                }

                var newFullPath = wtvi.FullPath;
                if (newFullPath.EndsWith("\\")) newFullPath = newFullPath.Substring(0, newFullPath.Length - 1);

                var lastindex = newFullPath.LastIndexOf("\\");
                if (lastindex >= 0) newFullPath = newFullPath.Substring(0, lastindex);

                newFullPath = newFullPath + "\\" + newShortName;

                if (File.Exists(newFullPath))
                {
                    LMessageBox.Show("　　已存在指定名称的文件，无法完成操作。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }

                try
                {
                    if (wtvi.IsMarkdownFilePath && newFullPath.ToLower().EndsWith(".md") == false) newFullPath += ".md";

                    File.Move(wtvi.FullPath, newFullPath);
                    var oldShortName = wtvi.ShortName;

                    var oldFilePath = wtvi.FullPath;
                    wtvi.FullPath = newFullPath;

                    //更新打开的文档的对应编辑器的FullPath
                    foreach (var item in Globals.MainWindow.mainTabControl.Items)
                    {
                        MarkdownEditor me = item as MarkdownEditor;
                        if (me == null) continue;

                        if (me.FullFilePath == oldFilePath)
                        {
                            me.FullFilePath = newFullPath;
                            break;
                        }
                    }

                    if (wtvi.FullPath.ToLower().EndsWith(".md"))
                    {
                        var parentDirectoryPath = wtvi.FullPath.Substring(0, wtvi.FullPath.LastIndexOf("\\"));
                        if (parentDirectoryPath.EndsWith("\\") == false) parentDirectoryPath += "\\";

                        var directories = new DirectoryInfo(parentDirectoryPath).GetDirectories();
                        foreach (var directory in directories)
                        {
                            var directoryShortName = oldShortName.Substring(0, oldShortName.Length - 3);
                            if (directory.FullName.EndsWith(directoryShortName) || directory.FullName.EndsWith(directoryShortName + "~"))
                            {
                                var dest = directory.FullName;
                                if (dest.EndsWith("\\")) dest.Substring(0, dest.Length - 1);

                                int lastIndex = dest.LastIndexOf("\\");
                                dest = dest.Substring(0, lastindex + 1);

                                directory.MoveTo(dest + newShortName + "~\\");
                                break;
                            }
                        }

                        var htmlFilePath = oldFilePath.Substring(0, oldFilePath.Length - 3) + ".html";

                        if (File.Exists(htmlFilePath))
                        {
                            File.Copy(htmlFilePath, parentDirectoryPath + newShortName + ".html", true);
                        }
                    }
                    WorkspaceManager.SaveWorkspaceTreeviewToXml();
                }
                catch (Exception ex)
                {
                    LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }
            }

            //workspaceManager.Refresh(wtvi.FullPath);
            //wtvi.RefreshFileState();
            //wtvi.FullPath 赋值时会自动调用上一行。

            //2017年7月21日，现在改由用户决定位置，此代码已不必存在，保留备查。
            #region 废弃代码
            //位置排序
            //var parentItem = wtvi.Parent as WorkspaceTreeViewItem;
            //if (parentItem != null)
            //{
            //    List<WorkspaceTreeViewItem> sortList = new List<LunarMarkdownEditor.WorkspaceTreeViewItem>();
            //    foreach (var item in parentItem.Items)
            //    {
            //        var ti = item as WorkspaceTreeViewItem;
            //        if (ti == null) continue;
            //        sortList.Add(ti);
            //    }

            //    var oldIndex = sortList.IndexOf(wtvi);

            //    sortList.Sort(new WorkspaceTreeViewItemCompare());

            //    var newIndex = sortList.IndexOf(wtvi);
            //    if (newIndex >= 0 && oldIndex >= 0)
            //    {
            //        parentItem.Items.Remove(wtvi);
            //        parentItem.Items.Insert(newIndex, wtvi);
            //    }
            //}
            #endregion

            wtvi.IsSelected = true;
        }

        /// <summary>
        /// 工作区管理器的快捷键处理。
        /// </summary>
        private void tvWorkDirectory_KeyDown(object sender, KeyEventArgs e)
        {
            switch (e.Key)
            {
                case Key.Delete:
                    {
                        miDeleteFile_Click(sender, e);
                        e.Handled = true;
                        break;
                    }
                case Key.Enter:
                    {
                        if (tvWorkDirectory.SelectedItem != null)
                        {
                            var wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
                            if (wtvi != null)
                            {
                                if (wtvi.IsMarkdownFilePath)
                                {
                                    wtvi.OpenFile();
                                    e.Handled = true;
                                }
                                else if (wtvi.IsDirectoryExists)
                                {
                                    wtvi.IsExpanded = true;
                                    e.Handled = true;
                                }
                                else if (wtvi.IsImageFileExist)
                                {
                                    wtvi.InsertImageTagToDocument(wtvi.FullPath);
                                    e.Handled = true;
                                }
                            }
                        }
                        break;
                    }
                case Key.F2:
                    {
                        miRename_Click(sender, e);
                        break;
                    }
            }
        }

        /// <summary>
        /// 格式化活动编辑器中的 Markdown 文本。
        /// </summary>
        private void miFormatDocument_Click(object sender, RoutedEventArgs e)
        {
            Format();
        }

        /// <summary>
        /// 格式化活动编辑器中的 Markdown 文本。
        /// </summary>
        private void Format()
        {
            if (this.mainTabControl.SelectedItem == null) return;

            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            eti.EditorBase.Format();
        }

        /// <summary>
        /// 根据路径查找工作区管理器中有无此条目。
        /// </summary>
        /// <param name="fullPath">要找的文件或目录的路径。</param>
        /// <returns>返回工作区管理器中某符合条件的条目。</returns>
        public WorkspaceTreeViewItem FindWorkspaceTreeViewItem(string fullPath)
        {
            if (string.IsNullOrEmpty(fullPath)) return null;

            if (IsMetaFilePath(fullPath))
            {
                FileInfo fi = new FileInfo(fullPath);
                fullPath = fi.DirectoryName;
                if (fullPath.EndsWith("\\") == false)
                {
                    fullPath += "\\";
                }
            }

            var baseItem = tvWorkDirectory.Items[0] as WorkspaceTreeViewItem;//根节点，必然存在。

            if (baseItem.FullPath.ToLower() == fullPath.ToLower()) return baseItem;

            foreach (var item in baseItem.Items)
            {
                WorkspaceTreeViewItem wtvi = item as WorkspaceTreeViewItem;
                if (wtvi == null) continue;

                if (wtvi.FullPath.ToLower() == fullPath.ToLower()) return wtvi;

                var resultItem = SearchWorkspaceTreeViewItem(wtvi, fullPath);
                if (resultItem != null) return resultItem;
            }

            return null;
        }

        /// <summary>
        /// 向工作区管理器中添加一个新条目。
        /// </summary>
        /// <param name="parentPath">基准位置父条目的“文件或文件夹路径”。</param>
        /// <param name="subPath">要添加的子文件或目录的路径。</param>
        public void InsertSubWorkspaceTreeViewItem(string parentPath, string subPath)
        {
            var parentItem = FindWorkspaceTreeViewItem(parentPath);

            if (parentItem == null)
            {
                if (parentPath.EndsWith("~") || parentPath.EndsWith("~\\"))
                {
                    //向资源文件夹条目下添加新资源子条目
                    DirectoryInfo parentDirectory = new DirectoryInfo(parentPath);
                    if (Directory.Exists(parentPath) == false)
                    {
                        parentDirectory = Directory.CreateDirectory(parentPath);
                    }

                    var preParent = parentDirectory.Parent;  //带MD元文件的目录条目、或者MD文件条目
                    var prePreParent = preParent.Parent;

                    if (preParent != null && preParent.Exists && prePreParent != null && prePreParent.Exists)
                    {
                        if (preParent.Name == "_" + prePreParent.Name + "~")
                        {
                            //目录元文件的资源目录
                            var prePreparentPath = prePreParent.FullName;
                            if (prePreparentPath.EndsWith("\\") == false)
                                prePreparentPath += "\\";
                            var prePreParentItem = FindWorkspaceTreeViewItem(prePreparentPath);
                            if (prePreParentItem != null)
                            {
                                WorkspaceTreeViewItem resourceItem = new LunarMarkdownEditor.WorkspaceTreeViewItem(parentPath, Globals.MainWindow);

                                #region 根据情况自动添加三类资源文件夹条目
                                if (resourceItem.IsImageResourceDirectory)
                                {
                                    prePreParentItem.Items.Insert(0, resourceItem);
                                }
                                else if (resourceItem.IsSoundResourceDirectory)
                                {
                                    WorkspaceTreeViewItem broImageResourceItem = null;
                                    foreach (var item in prePreParentItem.Items)
                                    {
                                        var wi = item as WorkspaceTreeViewItem;
                                        if (wi == null) continue;

                                        if (wi.IsImageResourceDirectory) { broImageResourceItem = wi; break; }
                                    }

                                    if (broImageResourceItem == null)
                                    {
                                        //没有 图像~ 资源文件夹条目，则声音在第0位置。
                                        prePreParentItem.Items.Insert(0, resourceItem);
                                    }
                                    else
                                    {
                                        //如果存在 图像~ 资源文件夹条目，放在它后面一个位置。
                                        prePreParentItem.Items.Insert(prePreParentItem.Items.IndexOf(broImageResourceItem) + 1, resourceItem);
                                    }
                                }
                                else if (resourceItem.IsVedioResourceDirectory)
                                {
                                    WorkspaceTreeViewItem broSoundResourceItem = null;
                                    foreach (var item in prePreParentItem.Items)
                                    {
                                        var wi = item as WorkspaceTreeViewItem;
                                        if (wi == null) continue;

                                        if (wi.IsSoundResourceDirectory) { broSoundResourceItem = wi; break; }
                                    }

                                    if (broSoundResourceItem == null)
                                    {
                                        //没有 声音~ 资源文件夹条目，还要看看是否有 图像~ 资源文件夹
                                        WorkspaceTreeViewItem broImageResourceItem = null;
                                        foreach (var item in prePreParentItem.Items)
                                        {
                                            var wi = item as WorkspaceTreeViewItem;
                                            if (wi == null) continue;

                                            if (wi.IsImageResourceDirectory) { broImageResourceItem = wi; break; }
                                        }

                                        if (broImageResourceItem == null)
                                        {
                                            //既没有 声音~ 又没有 图像~ 资源文件夹条目，则视频在第0位置。
                                            prePreParentItem.Items.Insert(0, resourceItem);
                                        }
                                        else
                                        {
                                            //如果没有 声音~ 但存在 图像~ 资源文件夹条目，放在 图像~ 后面一个位置。
                                            prePreParentItem.Items.Insert(prePreParentItem.Items.IndexOf(broImageResourceItem) + 1, resourceItem);
                                        }
                                    }
                                    else
                                    {
                                        //如果存在 声音~ 资源文件夹条目，放在它后面一个位置。
                                        prePreParentItem.Items.Insert(prePreParentItem.Items.IndexOf(broSoundResourceItem) + 1, resourceItem);
                                    }
                                }
                                else
                                {
                                    prePreParentItem.Items.Insert(0, resourceItem);
                                }
                                #endregion 根据情况自动添加三类资源文件夹条目

                                parentItem = resourceItem;
                            }
                        }
                        else
                        {
                            //普通文件的资源目录
                            //Images~、Sounds~、Vedios~目录的上级是资源文件目录（即由 MD文件短名~ 构成）；
                            var newSubResourceItem = new WorkspaceTreeViewItem(subPath, this);
                            var subResourceFolderItem = FindWorkspaceTreeViewItem(parentPath);
                            if (subResourceFolderItem == null)
                            {
                                var mdFilePath = parentDirectory.Parent.FullName.Substring(0, parentDirectory.Parent.FullName.Length - 1) + ".md";
                                var mdFileItem = FindWorkspaceTreeViewItem(mdFilePath);

                                subResourceFolderItem = new LunarMarkdownEditor.WorkspaceTreeViewItem(parentPath, this);

                                //mdFileItem.Items.Add(subResourceItem);

                                #region 根据情况自动添加三类资源文件夹条目
                                if (subResourceFolderItem.IsImageResourceDirectory)
                                {
                                    mdFileItem.Items.Insert(0, subResourceFolderItem);
                                }
                                else if (subResourceFolderItem.IsSoundResourceDirectory)
                                {
                                    WorkspaceTreeViewItem broImagenewSubResourceItem = null;
                                    foreach (var item in mdFileItem.Items)
                                    {
                                        var wi = item as WorkspaceTreeViewItem;
                                        if (wi == null) continue;

                                        if (wi.IsImageResourceDirectory) { broImagenewSubResourceItem = wi; break; }
                                    }

                                    if (broImagenewSubResourceItem == null)
                                    {
                                        //没有 图像~ 资源文件夹条目，则声音在第0位置。
                                        mdFileItem.Items.Insert(0, subResourceFolderItem);
                                    }
                                    else
                                    {
                                        //如果存在 图像~ 资源文件夹条目，放在它后面一个位置。
                                        mdFileItem.Items.Insert(mdFileItem.Items.IndexOf(broImagenewSubResourceItem) + 1, subResourceFolderItem);
                                    }
                                }
                                else if (subResourceFolderItem.IsVedioResourceDirectory)
                                {
                                    WorkspaceTreeViewItem broSoundnewSubResourceItem = null;
                                    foreach (var item in mdFileItem.Items)
                                    {
                                        var wi = item as WorkspaceTreeViewItem;
                                        if (wi == null) continue;

                                        if (wi.IsSoundResourceDirectory) { broSoundnewSubResourceItem = wi; break; }
                                    }

                                    if (broSoundnewSubResourceItem == null)
                                    {
                                        //没有 声音~ 资源文件夹条目，还要看看是否有 图像~ 资源文件夹
                                        WorkspaceTreeViewItem broImagenewSubResourceItem = null;
                                        foreach (var item in mdFileItem.Items)
                                        {
                                            var wi = item as WorkspaceTreeViewItem;
                                            if (wi == null) continue;

                                            if (wi.IsImageResourceDirectory) { broImagenewSubResourceItem = wi; break; }
                                        }

                                        if (broImagenewSubResourceItem == null)
                                        {
                                            //既没有 声音~ 又没有 图像~ 资源文件夹条目，则视频在第0位置。
                                            mdFileItem.Items.Insert(0, subResourceFolderItem);
                                        }
                                        else
                                        {
                                            //如果没有 声音~ 但存在 图像~ 资源文件夹条目，放在 图像~ 后面一个位置。
                                            mdFileItem.Items.Insert(mdFileItem.Items.IndexOf(broImagenewSubResourceItem) + 1, subResourceFolderItem);
                                        }
                                    }
                                    else
                                    {
                                        //如果存在 声音~ 资源文件夹条目，放在它后面一个位置。
                                        mdFileItem.Items.Insert(mdFileItem.Items.IndexOf(broSoundnewSubResourceItem) + 1, subResourceFolderItem);
                                    }
                                }
                                else
                                {
                                    mdFileItem.Items.Insert(0, subResourceFolderItem);
                                }
                                #endregion 根据情况自动添加三类资源文件夹条目
                            }

                            subResourceFolderItem.Items.Add(newSubResourceItem);

                        }
                    }
                }
            }

            if (parentItem == null) return;

            if (parentItem.IsSubWorkspaceTreeViewItemExists(subPath)) return;

            List<WorkspaceTreeViewItem> list = new List<WorkspaceTreeViewItem>();
            foreach (var item in parentItem.Items)
            {
                var wvItem = item as WorkspaceTreeViewItem;
                if (wvItem != null) list.Add(wvItem);
            }

            var newSubItem = new WorkspaceTreeViewItem(subPath, this);
            list.Add(newSubItem);
            list.Sort(new WorkspaceTreeViewItemCompare());

            var newIndex = list.IndexOf(newSubItem);
            parentItem.Items.Insert(newIndex, newSubItem);
        }

        /// <summary>
        /// 向工作区管理器中的某个条目下添加一个子级条目。
        /// </summary>
        /// <param name="parentItem">父条目。</param>
        /// <param name="subItem">子条目。</param>
        public void InsertSubWorkspaceTreeViewItem(WorkspaceTreeViewItem parentItem, WorkspaceTreeViewItem subItem)
        {
            if (parentItem == null || subItem == null) return;

            if (parentItem.Items.Contains(subItem)) return;
            if (parentItem.IsSubWorkspaceTreeViewItemExists(subItem.FullPath)) return;

            List<WorkspaceTreeViewItem> list = new List<WorkspaceTreeViewItem>();
            foreach (var item in parentItem.Items)
            {
                var wvItem = item as WorkspaceTreeViewItem;
                if (wvItem != null) list.Add(wvItem);
            }

            list.Add(subItem);
            list.Sort(new WorkspaceTreeViewItemCompare());

            var newIndex = list.IndexOf(subItem);
            parentItem.Items.Insert(newIndex, subItem);
        }

        /// <summary>
        /// 判断是否目录元文件。
        /// </summary>
        /// <param name="fullPath">文件路径。</param>
        /// <returns>以“_”开头的“.md”文件即返回真。</returns>
        private bool IsMetaFilePath(string fullPath)
        {
            try
            {
                FileInfo fi = new FileInfo(fullPath);
                if (fi.Name.StartsWith("_") && fi.Name.ToLower().EndsWith(".md"))
                {
                    return true;
                }

                return false;
            }
            catch
            {
                return false;
            }
        }

        /// <summary>
        /// 根据指定路径查找工作区管理器中对应的条目。
        /// </summary>
        /// <param name="item">从此条目开始查找。</param>
        /// <param name="fullPath">要查找的条目指向的磁盘路径。</param>
        /// <returns>返回找到的条目。</returns>
        private WorkspaceTreeViewItem SearchWorkspaceTreeViewItem(WorkspaceTreeViewItem item, string fullPath)
        {
            if (item == null || string.IsNullOrEmpty(fullPath)) return null;

            if (item.FullPath.ToLower() == fullPath.ToLower()) return item;

            foreach (var childItem in item.Items)
            {
                WorkspaceTreeViewItem wtvi = childItem as WorkspaceTreeViewItem;
                if (wtvi == null) continue;

                var resultItem = SearchWorkspaceTreeViewItem(wtvi, fullPath);
                if (resultItem != null) return resultItem;
            }

            return null;
        }

        /// <summary>
        /// 格式化当前活动编辑器当前编辑区附近的文字表。
        /// </summary>
        private void miFormatTextTable_Click(object sender, RoutedEventArgs e)
        {
            FormatAsTextTable();
        }

        /// <summary>
        /// 格式化当前活动编辑器当前编辑区附近的文字表。
        /// </summary>
        private void FormatAsTextTable()
        {
            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            eti.FormatTextTable();
        }

        /// <summary>
        /// 读取工作区设置的Html编译选项。
        /// </summary>
        private void LoadWorkspaceHtmlCompileOptions()
        {
            //是否启用任务列表项、二维文字表单元格、冒号开头的注释、材料出处、放大显示的文本行、步骤标记等对基本Markdown格式语法的支持。
            //不包括树型文字表——它是被当作代码块的，逻辑上冲突
            var enBaseMDSyntaxText = App.WorkspaceConfigManager.Get("EnableBaseMDSyntax");
            if (string.IsNullOrEmpty(enBaseMDSyntaxText) == false)
            {
                bool ebms;
                if (bool.TryParse(enBaseMDSyntaxText, out ebms))
                {
                    this.EnableBaseMDSyntax = ebms;
                }
            }
            else
            {
                this.EnableBaseMDSyntax = false;
            }
            miEnableBaseMDSyntax.IsChecked = this.EnableBaseMDSyntax;

            //编译 Html 使用的主题
            var s = App.WorkspaceConfigManager.Get("color");
            if (string.IsNullOrEmpty(s) == false)
            {
                foreach (var i in cmbColor.Items)
                {
                    ComboBoxItem cbi = i as ComboBoxItem;
                    if (cbi == null) continue;

                    if (cbi.Tag.ToString() == s)
                    {
                        cbi.IsSelected = true;
                        break;
                    }
                }
            }
            //cmbColor_SelectionChanged(this, null);

            //是否在 Html 末尾添加编译时间
            var appendTimeOfCompilingText = App.WorkspaceConfigManager.Get("AppendTimeOfCompiling");
            if (string.IsNullOrEmpty(appendTimeOfCompilingText) == false)
            {
                bool at;
                if (bool.TryParse(appendTimeOfCompilingText, out at))
                {
                    this.AppendTimeOfCompiling = at;
                }
            }
            else
            {
                this.AppendTimeOfCompiling = true;//默认开启
            }
            miAppendTimeOfCompiling.IsChecked = this.AppendTimeOfCompiling;

            //是否将图标题编译到顶部（默认底部）
            var imageTitleAtTopText = App.WorkspaceConfigManager.Get("ImageTitleAtTop");
            if (string.IsNullOrEmpty(imageTitleAtTopText) == false)
            {
                bool ttt;
                if (bool.TryParse(imageTitleAtTopText, out ttt))
                {
                    this.ImageTitleAtTop = ttt;
                }
            }
            else
            {
                this.ImageTitleAtTop = false;//默认在底部
            }
            miImageTitleAtTop.IsChecked = this.ImageTitleAtTop;

            //是否将表格标题编译到底部（默认顶部）
            var tableCaptionAtBottomText = App.WorkspaceConfigManager.Get("TableCaptionAtBottom");
            if (string.IsNullOrEmpty(tableCaptionAtBottomText) == false)
            {
                bool tcb;
                if (bool.TryParse(tableCaptionAtBottomText, out tcb))
                {
                    this.TableCaptionAtBottom = tcb;
                }
            }
            else
            {
                this.TableCaptionAtBottom = false;//默认在顶部
            }
            miTableCaptionAtBottom.IsChecked = this.TableCaptionAtBottom;



            //是否格式化编译好的 Html 文本
            var formatAfterCompileText = App.WorkspaceConfigManager.Get("FormatAfterCompile");
            if (string.IsNullOrEmpty(formatAfterCompileText) == false)
            {
                bool fac;
                if (bool.TryParse(formatAfterCompileText, out fac))
                {
                    this.FormatAfterCompile = fac;
                }
            }
            else
            {
                this.FormatAfterCompile = false;//默认关闭
            }
            miFormatAfterCompile.IsChecked = this.FormatAfterCompile;

            //指示编译后的 Html 文件中六级标题是否支持折叠、手工折叠还是自动折叠
            var htmlHeadersCollapse = App.WorkspaceConfigManager.Get("HtmlHeadersCollapse");
            if (string.IsNullOrEmpty(htmlHeadersCollapse) == false)
            {
                HtmlHeadersCollapseType hhct;
                var result = Enum.TryParse<HtmlHeadersCollapseType>(htmlHeadersCollapse, out hhct);
                if (result)
                {
                    this.HtmlHeadersCollapse = hhct;
                }
            }

            switch (this.HtmlHeadersCollapse)
            {
                case HtmlHeadersCollapseType.Auto:
                    {
                        miAutoCollapseHtmlHeaders.IsChecked = true;
                        miManualCollapseHtmlHeaders.IsChecked = false;
                        miNoCollapseHtmlHeaders.IsChecked = false;
                        break;
                    }
                case HtmlHeadersCollapseType.Manual:
                    {
                        miAutoCollapseHtmlHeaders.IsChecked = false;
                        miManualCollapseHtmlHeaders.IsChecked = true;
                        miNoCollapseHtmlHeaders.IsChecked = false;
                        break;
                    }
                //case HtmlHeadersCollapseType.NO:
                default:
                    {
                        miAutoCollapseHtmlHeaders.IsChecked = false;
                        miManualCollapseHtmlHeaders.IsChecked = false;
                        miNoCollapseHtmlHeaders.IsChecked = true;
                        break;
                    }
            }

            //编译 Html 六级标题时是否进行自动编号
            var autoNumberHeaders = App.WorkspaceConfigManager.Get("AutoNumberHeaders");
            if (string.IsNullOrEmpty(autoNumberHeaders) == false)
            {
                bool an;
                if (bool.TryParse(autoNumberHeaders, out an))
                {
                    this.AutoNumberHeaders = an;
                }
            }
            miAutoNumberHeaders.IsChecked = this.AutoNumberHeaders;

            var cleanHeadersText = App.WorkspaceConfigManager.Get("CleanHeaders");
            if (string.IsNullOrWhiteSpace(cleanHeadersText) == false)
            {
                bool ch;
                if (bool.TryParse(cleanHeadersText, out ch))
                {
                    this.CleanHeaders = ch;
                }
            }
            miCompileCleanHeaderLines.IsChecked = this.CleanHeaders;

            //miFillblankMode.IsChecked = !miFillblankMode.IsChecked;
            //this.WorkspaceConfigManager.Set("CompileCodeToFillBlank", miFillblankMode.IsChecked.ToString());
            //是否将 <code></code>块编译为支持填空的形式
            var compileCodeToFillBlank = App.WorkspaceConfigManager.Get("CompileCodeToFillBlank");
            if (string.IsNullOrEmpty(compileCodeToFillBlank) == false)
            {
                bool cctfb;
                if (bool.TryParse(compileCodeToFillBlank, out cctfb))
                {
                    this.CompileCodeToFillBlank = cctfb;
                }
            }
            miFillblankMode.IsChecked = this.CompileCodeToFillBlank;

            //是否强制为 Html 文件编译出左边栏菜单
            //如果为假，则根据页面内的设置文本决定是否编译左边栏菜单，格式是：
            //    ；[MENU]： 指定要编译左边栏菜单
            var compilePageMenu = App.WorkspaceConfigManager.Get("CompilePageMenu");
            if (string.IsNullOrEmpty(compilePageMenu) == false)
            {
                bool cpm;
                if (bool.TryParse(compilePageMenu, out cpm))
                {
                    this.CompilePageMenu = cpm;
                }
            }
            miCompilePageMenu.IsChecked = this.CompilePageMenu; ;

            //指示编译的 Html 文件中的试题是否应该隐藏答案
            var hideExamAnswer = App.WorkspaceConfigManager.Get("HideExamAnswer");
            if (string.IsNullOrEmpty(hideExamAnswer) == false)
            {
                bool hcqa;
                if (bool.TryParse(hideExamAnswer, out hcqa))
                {
                    this.HideExamAnswer = hcqa;
                }
            }
            miHideExamAnswer.IsChecked = this.HideExamAnswer;

            //指定编译的 Html 文件使用的编码方式
            var encoding = App.WorkspaceConfigManager.Get("Encoding");
            if (string.IsNullOrEmpty(encoding) == false)
            {
                RefreshEncoding(encoding);
            }
            else
            {
                RefreshEncoding("utf-8");
            }

            //编译工作区时是否忽略（跳过）加密文件
            var ignoreEncryptedFiles = App.WorkspaceConfigManager.Get("IgnoreEncryptedFiles");
            if (string.IsNullOrWhiteSpace(ignoreEncryptedFiles) == false)
            {
                bool ignoreEncryptedFilesValue;
                if (bool.TryParse(ignoreEncryptedFiles, out ignoreEncryptedFilesValue))
                {
                    this.IgnoreEncryptedFiles = ignoreEncryptedFilesValue;
                }
                else this.IgnoreEncryptedFiles = true;
            }
            else
            {
                this.IgnoreEncryptedFiles = true;
            }
            miIgnoreEncryptedFile.IsChecked = this.IgnoreEncryptedFiles;

            //编译工作区时是否忽略（跳过）被标记为“废弃”的文件
            var ignoreAbortedFiles = App.WorkspaceConfigManager.Get("IgnoreAbortedFiles");
            if (string.IsNullOrWhiteSpace(ignoreAbortedFiles) == false)
            {
                bool ignoreAbortedFilesValue;
                if (bool.TryParse(ignoreAbortedFiles, out ignoreAbortedFilesValue))
                {
                    this.IgnoreAbortedFiles = ignoreAbortedFilesValue;
                }
            }
            miIgnoreAbortedFile.IsChecked = this.IgnoreAbortedFiles;


        }

        private void RememberWorkspaceHtmlCompileOptions()
        {
            //编译 Html 使用的主题
            if (cmbColor.SelectedItem != null)
            {
                var cbi = cmbColor.SelectedItem as ComboBoxItem;
                App.WorkspaceConfigManager.Set("color", cbi.Tag.ToString());
            }
            else
            {
                App.WorkspaceConfigManager.Set("color", "light");
            }

            //是否格式化编译好的 Html 文本
            App.WorkspaceConfigManager.Set("FormatAfterCompile", this.FormatAfterCompile.ToString());

            //指示编译后的 Html 文件中六级标题是否支持折叠、手工折叠还是自动折叠
            App.WorkspaceConfigManager.Set("HtmlHeadersCollapse", this.HtmlHeadersCollapse.ToString());

            //编译 Html 六级标题时是否进行自动编号
            App.WorkspaceConfigManager.Set("AutoNumberHeaders", this.AutoNumberHeaders.ToString());

            //miFillblankMode.IsChecked = !miFillblankMode.IsChecked;
            //this.WorkspaceConfigManager.Set("CompileCodeToFillBlank", miFillblankMode.IsChecked.ToString());
            //是否将 <code></code>块编译为支持填空的形式
            App.WorkspaceConfigManager.Set("CompileCodeToFillBlank", CompileCodeToFillBlank.ToString());

            //是否强制为 Html 文件编译出左边栏菜单
            //如果为假，则根据页面内的设置文本决定是否编译左边栏菜单，格式是：
            //    ；[MENU]： 指定要编译左边栏菜单
            App.WorkspaceConfigManager.Set("CompilePageMenu", CompilePageMenu.ToString());

            //指示编译的 Html 文件中的试题是否应该隐藏答案
            App.WorkspaceConfigManager.Set("HideExamAnswer", HideExamAnswer.ToString());

            //指定编译的 Html 文件使用的编码方式
            App.WorkspaceConfigManager.Set("Encoding", defaultEncoding);

            //编译工作区时是否忽略（跳过）加密文件
            App.WorkspaceConfigManager.Set("IgnoreEncryptedFiles", IgnoreEncryptedFiles.ToString());
        }

        public event EventHandler<WorkspaceChangedEventArgs> WorkspaceChanged;

        protected void OnWorkspaceChanged(object sender, WorkspaceChangedEventArgs e)
        {
            if (WorkspaceChanged != null)
            {
                WorkspaceChanged(sender, e);
            }
        }

        /// <summary>
        /// 更改当前工作区目录。
        /// </summary>
        /// <param name="newWorkspacePath">新工作区目录的路径。</param>
        public void ChangeWorkspace(string newWorkspacePath = null)
        {
            var eventArgs = new WorkspaceChangedEventArgs()
            {
                OldWorkspaceDirectoryFullPath = Globals.PathOfWorkspace,
            };

            string destWorkspaceFolder = newWorkspacePath;

            if (destWorkspaceFolder == null)
            {
                //选择新工作目录
                System.Windows.Forms.FolderBrowserDialog fbd = new System.Windows.Forms.FolderBrowserDialog()
                {
                    Description = "　　请选择一个目录作为新的工作区目录（要求路径中不能含半角英文空格）。\r\n　　一些必要的css、js和图像文件会被复制到指定的目录中。",
                    ShowNewFolderButton = true,
                };
                System.Windows.Interop.HwndSource source = PresentationSource.FromVisual(this) as System.Windows.Interop.HwndSource;

                System.Windows.Forms.IWin32Window win = new WinFormWindow(source.Handle);
                System.Windows.Forms.DialogResult result = fbd.ShowDialog(win);
                if (result.Equals(System.Windows.Forms.DialogResult.OK) && Directory.Exists(fbd.SelectedPath))
                {
                    if (fbd.SelectedPath.Contains(" "))
                    {
                        var result2 = LMessageBox.Show("　　您选择的新目录的完整路径中含有空格，这会导致调用 Html Help Workshop 时出现一些小意外。\r\n　　您确定要这样做吗？",
                        Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Warning, "NPA_UseSpecialCharForFileName");
                        // NPA_ means No Prompt Again.

                        if (result2 != MessageBoxResult.Yes) return;
                    }

                    destWorkspaceFolder = fbd.SelectedPath;
                }
                else return;//相当于用户取消了操作。
            }
            else
            {
                if (newWorkspacePath.Length > 0)
                {
                    if (newWorkspacePath[newWorkspacePath.Length - 1] != Path.DirectorySeparatorChar)
                        newWorkspacePath += Path.DirectorySeparatorChar;
                }

                if (Directory.Exists(destWorkspaceFolder) == false)
                {
                    var result = LMessageBox.Show("　　物理磁盘上不存在该目录，要创建一个新目录吗？", Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Question);
                    if (result != MessageBoxResult.Yes) return;

                    if (destWorkspaceFolder.Contains(" "))
                    {
                        var result2 = LMessageBox.Show("　　目标目录的完整路径中含有空格，这会导致调用 Html Help Workshop 时出现一些小意外。\r\n　　你确定要这样做吗？",
                                            Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Warning, "NPA_UseSpecialCharForFileName");
                        if (result2 != MessageBoxResult.Yes) return;
                    }

                    System.IO.Directory.CreateDirectory(destWorkspaceFolder);
                }
            }

            var destEditingFilePath = destWorkspaceFolder + "\\~.editing";
            if (File.Exists(destEditingFilePath))
            {
                var result = LMessageBox.Show(
                    "　　此工作区貌似已经处于打开状态或者上次编辑此工作区时程序曾经意外崩溃。\r\n\r\n" +
                    "　　重复打开工作区易造成意外错误，真的要继续吗？",
                    Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Warning, "", null, null);
                if (result != MessageBoxResult.Yes)
                    return;
            }

            //[废弃]记录工作区管理器中各项的“展开/折叠”状态。已由WorkspaceItems.xml文件接管。
            //SaveWorkspaceCollapseStatus();

            //检查文件保存状态
            try
            {
                List<MarkdownEditor> needSavingDocumentList = new List<MarkdownEditor>();
                foreach (var item in this.mainTabControl.Items)
                {
                    MarkdownEditor eti = item as MarkdownEditor;
                    if (eti != null && eti.IsModified)
                    {
                        needSavingDocumentList.Add(eti);
                    }
                }

                if (needSavingDocumentList.Count > 0)
                {
                    MessageBoxResult r = LMessageBox.Show(string.Format("　　有 {0} 个文档已被修改，要保存吗？", needSavingDocumentList.Count),
                        Globals.AppName, MessageBoxButton.YesNoCancel, MessageBoxImage.Warning);
                    switch (r)
                    {
                        case MessageBoxResult.Yes:
                            {
                                string saveInfo;
                                foreach (MarkdownEditor eti in needSavingDocumentList)
                                {
                                    saveInfo = eti.SaveDocument();
                                    if (saveInfo == "用户取消操作")
                                    {
                                        return;//不更改工作目录
                                    }
                                    else if (saveInfo != string.Empty)
                                    {
                                        LMessageBox.Show(saveInfo, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                                        return;//不更改工作目录
                                    }
                                }

                                this.mainTabControl.Items.Clear();
                                break;
                            }
                        case MessageBoxResult.Cancel: return;//不更改工作目录。
                        default://No,放弃保存，更改工作目录。
                            {
                                break;
                            }
                    }
                }

                //记录下当前打开的文档列表。
                RememberOpenedFilesForWorkspace();

                //记录当前工作区 Html 编译选项。
                RememberWorkspaceHtmlCompileOptions();

                this.mainTabControl.Items.Clear();

                //用于切换工作区。
                var oldEditingFilePath = Globals.PathOfWorkspace + "\\~.editing";
                if (File.Exists(oldEditingFilePath))
                {
                    File.Delete(oldEditingFilePath);
                }

                if (File.Exists(oldEditingFilePath))
                {
                    File.Delete(oldEditingFilePath);
                }

                App.WorkspaceConfigManager = null;//重置工作区配置文件管理器。

                //tvWorkDirectory.IsEnabled = false;

                //下面才载入新工作区

                //用于判断当前工作区目录是否已被打开，以便提示用户不要重复打开工作区
                File.WriteAllText(destEditingFilePath, "This workspace is in editing.");

                App.ConfigManager.Set("Workspace", destWorkspaceFolder);
                Globals.PathOfWorkspace = destWorkspaceFolder;
                this.tbxPathOfWorkspace.Text = destWorkspaceFolder;

                CopyCssAndResourceFiles(destWorkspaceFolder);

                if (this.WorkspaceManager.LoadWorkspaceFromXml(null) == false)
                {
                    this.WorkspaceManager.RefreshWorkspaceTreeView(null);       //已限制
                }

                previewFrame.Source = null;

                //记载到一个列表文件中
                // var lines = File.ReadAllLines(Globals.PathOfHistoryWorkspaceFileFullName);
                WriteHistoryWorkspacesFile(destWorkspaceFolder);

                for (int i = lbxHistoryWorkspaces.Items.Count - 1; i >= 0; i--)
                {
                    var rwitem = lbxHistoryWorkspaces.Items[i] as RecentDirectoryListBoxItem;
                    if (rwitem != null && rwitem.DirectoryPath == destWorkspaceFolder)
                    {
                        lbxHistoryWorkspaces.Items.Remove(rwitem);
                    }
                }

                var rw = new RecentDirectoryListBoxItem(destWorkspaceFolder)
                {
                    ToolTip = "双击设置为当前工作区目录",
                };
                rw.MouseDoubleClick += rw_MouseDoubleClick;

                lbxHistoryWorkspaces.Items.Insert(0, rw);
                lbxHistoryWorkspaces.SelectedIndex = 0;

                tcManagerPanels.SelectedItem = tiWorkspace;

                RefreshWorkspaceHistoryList();

                LoadRememberOpenedFiles();//载入上次打开的文件。

                //[废弃]WorkspaceItems.xml接管了记录工作区管理器中各条目折叠状态的记录。
                //LoadWorkspaceCollapseStatus();

                LoadWorkspaceHtmlCompileOptions();//载入工作区 Html 编译选项。

                //tvWorkDirectory.IsEnabled = true;

                //如果有必要，刷新对照区。
                if (this.PerspectiveMode == Perspective.CompareMode)
                {
                    var activeEditor = ActivedEditor;
                    if (activeEditor != null)
                    {
                        activeEditor.SendToRightCompareArea();
                    }
                }

                AddJumpTask(destWorkspaceFolder);

                //触发WorkspaceChanged事件
                eventArgs.NewWorkspaceDirectoryFullPath = Globals.PathOfWorkspace;
                OnWorkspaceChanged(this, eventArgs);

                //判断是否新建的工作区
                if (File.Exists(WorkspaceManager.WorkspaceTreeviewItemsXmlPath) == false)
                {
                    WorkspaceManager.SaveWorkspaceTreeviewToXml();
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Information);
            }
        }

        private void LoadRememberedPerspectiveMode()
        {
            var perspectiveModeText = App.ConfigManager.Get("PerspectiveMode");
            var perspectiveIndex = (int)Perspective.Normal;
            Perspective perspectiveMode = Perspective.Default;
            if (string.IsNullOrEmpty(perspectiveModeText) == false)
            {
                if (Enum.TryParse<Perspective>(perspectiveModeText, out perspectiveMode))
                {
                    perspectiveIndex = (int)perspectiveMode;
                }
            }
            cmbPerspective.SelectedIndex = perspectiveIndex;
            RefreshPersective(perspectiveMode);
        }

        /// <summary>
        /// 刷新历史工作区列表。
        /// </summary>
        private void RefreshWorkspaceHistoryList()
        {
            for (int i = 0; i < lbxHistoryWorkspaces.Items.Count; i++)
            {
                var item = lbxHistoryWorkspaces.Items[i] as RecentDirectoryListBoxItem;
                if (item == null) continue;

                item.IndexText = (i + 1).ToString();
            }
        }

        /// <summary>
        /// 载入上次关闭程序时打开的那些文件。需要开启“记住打开的文件”这个开关才应起作用。
        /// </summary>
        private void LoadRememberOpenedFiles()
        {
            if (this.RememberOpenedFiles == false) return;

            var openedFilesText = App.WorkspaceConfigManager.Get("OpenedFiles");
            var rememberedActiveDocumentFullPath = App.WorkspaceConfigManager.Get("ActiveDocumentFullPath");
            //文档初始化

            if (string.IsNullOrEmpty(openedFilesText) || this.RememberOpenedFiles == false) return;

            var openedFiles = openedFilesText.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);

            FileSelectorDialog fsd = new FileSelectorDialog(openedFiles)
            {
                Owner = this,
                WindowStartupLocation = WindowStartupLocation.CenterOwner,
            };
            if (fsd.ShowDialog() != true) return;

            if (fsd.SelectedFilePaths != null && fsd.SelectedFilePaths.Count > 0)
            {
                OpenDocuments(fsd.SelectedFilePaths);
            }

            bool shouldFocusFstFile = true;

            var activeDocumentFullPath = App.WorkspaceConfigManager.Get("ActiveDocumentFullPath");
            if (File.Exists(activeDocumentFullPath))
            {
                foreach (var item in this.mainTabControl.Items)
                {
                    var me = item as MarkdownEditor;
                    if (me == null || me.FullFilePath == null) continue;

                    if (me.FullFilePath.ToLower() == activeDocumentFullPath.ToLower())
                    {
                        this.mainTabControl.SelectedItem = me;
                        shouldFocusFstFile = false;
                        break;
                    }
                }
            }

            if (shouldFocusFstFile && this.mainTabControl.Items.Count > 0)
            {
                this.mainTabControl.SelectedIndex = 0;
            }

            if (string.IsNullOrWhiteSpace(rememberedActiveDocumentFullPath) == false &&
                File.Exists(rememberedActiveDocumentFullPath))
            {
                int index = -1;
                for (int i = 0; i < this.mainTabControl.Items.Count; i++)
                {
                    var editor = this.mainTabControl.Items[i] as MarkdownEditor;
                    if (editor == null) continue;

                    if (editor.FullFilePath == rememberedActiveDocumentFullPath)
                    {
                        index = i;
                        break;
                    }
                }

                if (index >= 0)
                {
                    this.mainTabControl.SelectedIndex = index;
                }
            }
        }

        /// <summary>
        /// 重写“历史工作区列表”到文件中。会将新选中的工作区移动到历史工作区列表的头部。
        /// </summary>
        /// <param name="destWorkspaceFolder">新工作区目录的路径。</param>
        private static void WriteHistoryWorkspacesFile(string destWorkspaceFolder)
        {
            if (destWorkspaceFolder.EndsWith("\\"))
            {
                destWorkspaceFolder = destWorkspaceFolder.Substring(0, destWorkspaceFolder.Length - 1);
            }

            destWorkspaceFolder = destWorkspaceFolder.ToLower();

            if (File.Exists(Globals.PathOfHistoryWorkspaceFileFullName))
            {
                string[] lines = File.ReadAllLines(Globals.PathOfHistoryWorkspaceFileFullName, new UnicodeEncoding());
                var newLinesList = new List<string>();
                foreach (var line in lines)
                {
                    string tmp;
                    if (line.EndsWith("\\"))
                    {
                        tmp = line.Substring(0, line.Length - 1).ToLower();
                    }
                    else tmp = line.ToLower();

                    if (tmp == destWorkspaceFolder) continue;

                    newLinesList.Add(line);
                }

                var newLines = new string[newLinesList.Count + 1];
                newLines[0] = destWorkspaceFolder;
                for (int i = 1; i < newLines.Length; i++)
                {
                    newLines[i] = newLinesList[i - 1];
                }

                File.WriteAllLines(Globals.PathOfHistoryWorkspaceFileFullName, newLines, new UnicodeEncoding());
            }
            else
            {
                File.WriteAllLines(Globals.PathOfHistoryWorkspaceFileFullName, new string[] { destWorkspaceFolder }, new UnicodeEncoding());
            }
        }

        /// <summary>
        /// 更改所有编辑器默认字体。
        /// </summary>
        private void fontFamilyList_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (fontFamilyList.SelectedItem == null) return;
            var cbi = fontFamilyList.SelectedItem as ComboBoxItem;
            if (cbi == null) return;
            var item = cbi.Content as FontFamilyListItem;
            if (item == null) return;

            //右工具栏中的对照区不能直接对右工具栏变化字号
            foreach (var ue in this.tcRightToolBar.Items)
            {
                MarkdownEditor eti = ue as MarkdownEditor;
                if (eti == null) continue;

                eti.EditorBase.FontFamily = item.FontFamily;
            }

            //左工具栏中的对照区不能直接对左工具栏变化字号
            foreach (var ue in this.tcManagerPanels.Items)
            {
                MarkdownEditor eti = ue as MarkdownEditor;
                if (eti == null) continue;

                eti.EditorBase.FontFamily = item.FontFamily;
            }

            this.mainTabControl.FontFamily = item.FontFamily;
            this.tvOutLine.FontFamily = item.FontFamily;

            string fontEnName;
            if (item.FontFamily.FamilyNames.TryGetValue(System.Windows.Markup.XmlLanguage.GetLanguage("en-us"), out fontEnName))
            {
                App.ConfigManager.Set("FontFamily", fontEnName);
            }
        }

        /// <summary>
        /// 显示帮助页。
        /// </summary>
        private void miHelp_Click(object sender, RoutedEventArgs e)
        {
            ShowHelp();
        }

        /// <summary>
        /// 显示帮助页。
        /// </summary>
        private void ShowHelp()
        {
            tcRightToolBar.SelectedItem = tiHelp;
            if (tcRightToolBar.ActualWidth < 100)
            {
                cdMainEditArea.Width =
                    cdRightToolsArea.Width = new GridLength(3, GridUnitType.Star);
            }
        }

        /// <summary>
        /// 显示/隐藏左工具栏。
        /// </summary>
        private void bdLeftToolsController_Click(object sender, RoutedEventArgs e)
        {
            SwitchLeftToolBarToggle();
        }

        /// <summary>
        /// 显示/隐藏左工具栏。
        /// </summary>
        public void SwitchLeftToolBarToggle()
        {
            if (cdLeftToolsArea.ActualWidth > 100)
            {
                cdLeftToolsArea.Width = new GridLength(0);
            }
            else
            {
                cdLeftToolsArea.Width = new GridLength(360, GridUnitType.Pixel);
            }
        }

        /// <summary>
        /// 显示/隐藏右工具栏。
        /// </summary>
        internal void SwitchRightToolBarToggle()
        {
            if (cdRightToolsArea.ActualWidth > 100)
            {
                cdRightToolsArea.Width = new GridLength(0);
            }
            else
            {
                cdMainEditArea.Width =
                    cdRightToolsArea.Width = new GridLength(3, GridUnitType.Star);
            }
        }

        /// <summary>
        /// 显示/隐藏“查找/替换”面板。
        /// </summary>
        internal void SwitchFindAndReplacePanelToggle()
        {
            if (rdFindAndReplace.ActualHeight > 0)
            {
                rdFindAndReplace.Height = new GridLength(0);
            }
            else
            {
                rdFindAndReplace.Height = new GridLength(140, GridUnitType.Auto);
                cmbFindText.UpdateLayout();
            }
        }

        /// <summary>
        /// 显示、隐藏右工具栏。
        /// </summary>
        private void bdRightToolsController_Click(object sender, RoutedEventArgs e)
        {
            SwitchRightToolBarToggle();
        }

        /// <summary>
        /// 切换“保存前自动格式化Markdown文本”的开关。
        /// </summary>
        private void miFormatBeforeSave_Click(object sender, RoutedEventArgs e)
        {
            this.miFormatBeforeSave.IsChecked = !this.miFormatBeforeSave.IsChecked;
            App.ConfigManager.Set("AutoFormat", miFormatBeforeSave.IsChecked.ToString());
        }

        private bool rememberOpenedFiles = false;
        /// <summary>
        /// 决定要不要下次启动时自动打开上次关闭时打开的那些文件。
        /// </summary>
        public bool RememberOpenedFiles
        {
            get { return rememberOpenedFiles; }
            set { rememberOpenedFiles = value; }
        }

        /// <summary>
        /// 切换“记住打开的文档”属性的值。如果为真，下次启动时会自动打开上次关闭时打开的那些文件。
        /// </summary>
        private void miRememberOpenedFiles_Click(object sender, RoutedEventArgs e)
        {
            miRememberOpenedFiles.IsChecked = !miRememberOpenedFiles.IsChecked;
            this.RememberOpenedFiles = miRememberOpenedFiles.IsChecked;
            App.ConfigManager.Set("RememberOpenedFiles", miRememberOpenedFiles.IsChecked.ToString());
        }

        /// <summary>
        /// 显示帮助页。
        /// </summary>
        private void btnHelp_Click(object sender, RoutedEventArgs e)
        {
            ShowHelp();
        }

        private bool selectCellFirst = false;
        /// <summary>
        /// 编辑时按Tab键，决定是否选选中当前文字表单元格中所有文本。
        /// </summary>
        public bool SelectCellFirst
        {
            get { return selectCellFirst; }
            set { selectCellFirst = value; }
        }

        public enum HtmlHeadersCollapseType { Auto, Manual, No }

        private HtmlHeadersCollapseType htmlHeadersCollapse = MainWindow.HtmlHeadersCollapseType.Manual;
        /// <summary>
        /// 决定编译后的 Html 文档中的各标题是否自动折叠。
        /// </summary>
        public HtmlHeadersCollapseType HtmlHeadersCollapse
        {
            get { return htmlHeadersCollapse; }
            set { htmlHeadersCollapse = value; }
        }

        private bool autoNumberHeaders = true;
        /// <summary>
        /// 决定是否让编译后的 Html 文档中的六级标题都支持自动编号。
        /// </summary>
        public bool AutoNumberHeaders
        {
            get { return autoNumberHeaders; }
            set { autoNumberHeaders = value; }
        }

        private bool cleanHeaders = false;
        /// <summary>
        /// 指定编译后的六级标题是否不带格式。如果为真，编译后的标题只会比平常字号稍大些。
        /// </summary>
        public bool CleanHeaders
        {
            get { return cleanHeaders; }
            set { cleanHeaders = value; }
        }

        private bool compileCodeToFillBlank = false;
        /// <summary>
        /// 决定是否将编译后的 Html 文档中的行内代码块初始色彩设置为透明色——这样就呈现出填空题的效果。
        /// </summary>
        public bool CompileCodeToFillBlank
        {
            get { return compileCodeToFillBlank; }
            set { compileCodeToFillBlank = value; }
        }

        private bool compilePageMenu = false;
        /// <summary>
        /// 决定是否为 Html 网页强制编译左边栏菜单——否则须文档中带 ；[Menu]： 标记行才可以。
        /// </summary>
        public bool CompilePageMenu
        {
            get { return compilePageMenu; }
            set { compilePageMenu = value; }
        }

        private bool hideExamAnswer = true;
        /// <summary>
        /// 决定编译后的 Html 文档中的各试题是否隐藏答案。
        /// </summary>
        public bool HideExamAnswer
        {
            get { return hideExamAnswer; }
            set { hideExamAnswer = value; }
        }

        private bool textAutoWrap = true;
        /// <summary>
        /// 决定编辑器中的文本是否自动折行的开关。
        /// </summary>
        public bool TextAutoWrap
        {
            get { return this.textAutoWrap; }
            set
            {
                this.textAutoWrap = value;
                RefreshTextAutoWrap();
            }
        }

        /// <summary>
        /// 刷新各编辑器文本自动折行状态。
        /// </summary>
        private void RefreshTextAutoWrap()
        {
            foreach (var item in this.mainTabControl.Items)
            {
                var editor = item as MarkdownEditor;
                if (editor == null) continue;

                editor.EditorBase.WordWrap = this.textAutoWrap;
            }
        }

        /// <summary>
        /// 将当前编辑器的文本折行状态刷新到状态条上。
        /// </summary>
        public void RefreshTextAutoWrapToStatusBar()
        {
            if (mainTabControl.HasItems == false)
            {
                bdAutoWrap.Visibility = Visibility.Hidden;
                return;
            }

            var selItem = mainTabControl.SelectedItem as MarkdownEditor;
            if (selItem == null)
            {
                bdAutoWrap.Visibility = Visibility.Hidden;
                return;
            }

            bdAutoWrap.Visibility = Visibility.Visible;
            if (selItem.EditorBase.WordWrap)
            {
                bdAutoWrap.Background = Brushes.White;
            }
            else bdAutoWrap.Background = Brushes.Transparent;
        }

        private bool enableBaseMDSyntax = false;//默认关闭
        /// <summary>
        /// 是否启用任务列表项、二维文字表单元格等元素中对基本Markdown格式语法的支持。默认关闭。
        /// </summary>
        public bool EnableBaseMDSyntax
        {
            get { return this.enableBaseMDSyntax; }
            set { this.enableBaseMDSyntax = value; }
        }

        private bool appendTimeOfCompiling = false;
        /// <summary>
        /// 是否在编译的 Html 文档末尾添加编译时间。
        /// </summary>
        public bool AppendTimeOfCompiling
        {
            get { return this.appendTimeOfCompiling; }
            set { this.appendTimeOfCompiling = value; }
        }

        private bool imageTitleAtTop = false;
        /// <summary>
        /// 是否将图的标题编译到顶部。默认底部。
        /// </summary>
        public bool ImageTitleAtTop
        {
            get { return this.imageTitleAtTop; }
            set { this.imageTitleAtTop = value; }
        }

        private bool tableCaptionAtBottom = false;
        /// <summary>
        /// 是否将表格标题编译到表格底部。默认顶部。
        /// </summary>
        public bool TableCaptionAtBottom
        {
            get { return this.tableCaptionAtBottom; }
            set { this.tableCaptionAtBottom = value; }
        }

        private bool formatAfterCompile = false;
        /// <summary>
        /// 是否对编译后的Html文档进行格式化。
        /// </summary>
        public bool FormatAfterCompile
        {
            get { return this.formatAfterCompile; }
            set { this.formatAfterCompile = value; }
        }

        private bool isExamEnabled = true;
        /// <summary>
        /// 是否启用“试题编辑”功能。
        /// </summary>
        public bool IsExamEnabled
        {
            get
            {
                return isExamEnabled;
            }

            set
            {
                isExamEnabled = value;
                foreach (var i in mainTabControl.Items)
                {
                    var me = i as MarkdownEditor;
                    if (me == null) continue;

                    me.IsExamEnabled = value;
                }

                if (isExamEnabled)
                {
                    mExams.Visibility = Visibility.Visible;
                    tabValidateDocument.Visibility = Visibility.Visible;
                }
                else
                {
                    mExams.Visibility = Visibility.Collapsed;
                    tabValidateDocument.Visibility = Visibility.Collapsed;
                }
            }
        }

        private bool isAutoCompletionEnabled = false;
        /// <summary>
        /// 是否启用“自动完成”功能。
        /// </summary>
        public bool IsAutoCompletionEnabled
        {
            get
            {
                return isAutoCompletionEnabled;
            }

            set
            {
                isAutoCompletionEnabled = value;
                foreach (var i in mainTabControl.Items)
                {
                    var me = i as MarkdownEditor;
                    if (me == null) continue;

                    me.IsAutoCompletionEnabled = value;
                }
            }
        }

        private bool isEnToChineseDictEnabled = false;
        /// <summary>
        /// 英译中词典条目自动提示功能是否启用的开关。
        /// </summary>
        public bool IsEnToChineseDictEnabled
        {
            get
            {
                return isEnToChineseDictEnabled;
            }

            set
            {
                isEnToChineseDictEnabled = value;

                foreach (var i in mainTabControl.Items)
                {
                    var me = i as MarkdownEditor;
                    me.IsEnToChineseDictEnabled = value;
                }

                if (value)
                {
                    LoadDictionary();
                }
            }
        }

        private MarkDownEditorBase.HighlightingType highlightingSetting = MarkDownEditorBase.HighlightingType.Advance;
        /// <summary>
        /// 采用何种高亮显示方案。
        /// </summary>
        public MarkDownEditorBase.HighlightingType HighlightingSetting
        {
            get { return highlightingSetting; }
            set
            {
                highlightingSetting = value;

                foreach (var i in mainTabControl.Items)
                {
                    var me = i as MarkdownEditor;
                    me.HighlightingSetting = value;
                }
            }
        }

        private bool isShowSpaces = false;
        /// <summary>
        /// 决定是否显示空格。
        /// </summary>
        public bool IsShowSpaces
        {
            get { return isShowSpaces; }
            set
            {
                isShowSpaces = value;
                foreach (var i in mainTabControl.Items)
                {
                    var me = i as MarkdownEditor;
                    me.IsShowSpaces = value;
                }

                foreach (var i in tcRightToolBar.Items)
                {
                    var me = i as MarkdownEditor;
                    if (me != null) me.IsShowSpaces = value;
                }
            }
        }

        private bool isShowEndOfLine = false;
        /// <summary>
        /// 决定是否显示空格。
        /// </summary>
        public bool IsShowEndOfLine
        {
            get { return isShowEndOfLine; }
            set
            {
                isShowEndOfLine = value;
                foreach (var i in mainTabControl.Items)
                {
                    var me = i as MarkdownEditor;
                    me.IsShowEndOfLine = value;
                }

                foreach (var i in tcRightToolBar.Items)
                {
                    var me = i as MarkdownEditor;
                    if (me != null) me.IsShowEndOfLine = value;
                }
            }
        }

        private bool isShowTabs = false;
        /// <summary>
        /// 决定是否显示空格。
        /// </summary>
        public bool IsShowTabs
        {
            get { return isShowTabs; }
            set
            {
                isShowTabs = value;
                foreach (var i in mainTabControl.Items)
                {
                    var me = i as MarkdownEditor;
                    me.IsShowTabs = value;
                }

                foreach (var i in tcRightToolBar.Items)
                {
                    var me = i as MarkdownEditor;
                    if (me != null) me.IsShowTabs = value;
                }
            }
        }

        private bool isShowLineNumbers = false;
        /// <summary>
        /// 决定是否显示空格。
        /// </summary>
        public bool IsShowLineNumbers
        {
            get { return isShowLineNumbers; }
            set
            {
                isShowLineNumbers = value;
                foreach (var i in mainTabControl.Items)
                {
                    var me = i as MarkdownEditor;
                    me.IsShowLineNumbers = value;
                }

                foreach (var i in tcRightToolBar.Items)
                {
                    var me = i as MarkdownEditor;
                    if (me != null) me.IsShowLineNumbers = value;
                }
            }
        }


        private bool showTitleInWorkspaceManager = false;
        /// <summary>
        /// 在工作区管理器中是显示文件短名还是显示文档中设置的“标题”文本。
        /// 文件短名利于看出顺序；标题则更美观些，更直观。
        /// </summary>
        public bool ShowTitleInWorkspaceManager
        {
            get { return showTitleInWorkspaceManager; }
            set
            {
                showTitleInWorkspaceManager = value;
                foreach (var item in tvWorkDirectory.Items)
                {
                    var ti = item as TreeViewItem;
                    if (ti != null)
                    {
                        RefreshWorkspaceTreeViewItemTitles(ti, value);
                    }
                }
            }
        }

        /// <summary>
        /// 刷新工作区管理器中所有项目的标题。
        /// </summary>
        public void RefreshWorkspaceTreeViewItemTitles(TreeViewItem item, bool value)
        {
            var wi = item as WorkspaceTreeViewItem;
            if (wi != null)
            {
                wi.ShowTitle = value;
            }

            foreach (var subItem in item.Items)
            {
                var sti = subItem as TreeViewItem;
                if (sti != null)
                {
                    RefreshWorkspaceTreeViewItemTitles(sti, value);
                }
            }
        }

        /// <summary>
        /// 载入词典条目。
        /// </summary>
        private void LoadDictionary()
        {
            dictionary.Clear();
            LoadDictionaryFromFile(Globals.PathOfUserDictionary);
            LoadDictionaryFromFile(Globals.PathOfWorkspaceDictionary);

            foreach (var i in this.mainTabControl.Items)
            {
                var edit = i as MarkdownEditor;
                if (edit == null) continue;

                edit.EditorBase.InvalidateCompletionWindow();
            }
        }

        /// <summary>
        /// 从指定词典文件中载入词典。
        /// </summary>
        /// <param name="filePath">词典文件路径。</param>
        private void LoadDictionaryFromFile(string filePath)
        {
            if (File.Exists(filePath) == false) return;

            using (StreamReader sr = new StreamReader(filePath))
            {
                var line = sr.ReadLine();
                var splitter = new char[] { '\t' };
                while (string.IsNullOrEmpty(line) == false)
                {
                    if (line.StartsWith(";"))//以分号开头的是注释行
                    {
                        line = sr.ReadLine();
                        continue;
                    }

                    EnToChEntry entry = new EnToChEntry();
                    string[] pieces = line.Split(splitter, StringSplitOptions.RemoveEmptyEntries);
                    int i = 0;
                    if (i < pieces.Length) entry.English = pieces[i];
                    i = 1;
                    if (i < pieces.Length) entry.Chinese = pieces[i];
                    i = 2;
                    if (i < pieces.Length) entry.Alpha = pieces[i];
                    i = 3;
                    if (i < pieces.Length) entry.Comment = pieces[i];
                    dictionary.Add(entry);
                    line = sr.ReadLine();
                }
            }
        }

        /// <summary>
        /// 切换“SelectCellFirst”属性的值。此属性的值将决定
        /// 在编辑文档时，按下Tab键的行为（在文字表中是选选中当前单元格所有文本还是直接跳转到后一单元格）。
        /// </summary>
        private void miSelectCellFirst_Click(object sender, RoutedEventArgs e)
        {
            miSelectCellFirst.IsChecked = !miSelectCellFirst.IsChecked;
            this.SelectCellFirst = miSelectCellFirst.IsChecked;
            App.ConfigManager.Set("SelectCellFirst", miSelectCellFirst.IsChecked.ToString());
        }

        /// <summary>
        /// 切换“HtmlHeadersCollapse”属性的值。此属性的值将决定编译后的 Html 文件中的各标题是：“自动折叠”、“手动折叠”还是“不折叠”。
        /// </summary>
        private void miAutoCollapseHtmlHeaders_Click(object sender, RoutedEventArgs e)
        {
            if (CompilePageMenu)
            {
                var result = LMessageBox.Show("　　【自动折叠标题】选项会导致左边栏菜单中的链接不起作用（除非用户展开这些标题）。\r\n\r\n　　真的要继续吗？",
                    Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Warning);
                if (result != MessageBoxResult.Yes) return;
            }

            miAutoCollapseHtmlHeaders.IsChecked = true;
            miManualCollapseHtmlHeaders.IsChecked = false;
            miNoCollapseHtmlHeaders.IsChecked = false;

            this.HtmlHeadersCollapse = HtmlHeadersCollapseType.Auto;
            App.WorkspaceConfigManager.Set("HtmlHeadersCollapse", this.HtmlHeadersCollapse.ToString());
        }

        /// <summary>
        /// 切换“HtmlHeadersCollapse”属性的值。此属性的值将决定编译后的 Html 文件中的各标题是：“自动折叠”、“手动折叠”还是“不折叠”。
        /// </summary>
        private void miManualCollapseHtmlHeaders_Click(object sender, RoutedEventArgs e)
        {
            miManualCollapseHtmlHeaders.IsChecked = true;
            miAutoCollapseHtmlHeaders.IsChecked = false;
            miNoCollapseHtmlHeaders.IsChecked = false;

            this.HtmlHeadersCollapse = HtmlHeadersCollapseType.Manual;
            App.WorkspaceConfigManager.Set("HtmlHeadersCollapse", this.HtmlHeadersCollapse.ToString());
        }

        /// <summary>
        /// 切换“HtmlHeadersCollapse”属性的值。此属性的值将决定编译后的 Html 文件中的各标题是：“自动折叠”、“手动折叠”还是“不折叠”。
        /// </summary>
        private void miNoCollapseHtmlHeaders_Click(object sender, RoutedEventArgs e)
        {
            miNoCollapseHtmlHeaders.IsChecked = true;
            miAutoCollapseHtmlHeaders.IsChecked = false;
            miManualCollapseHtmlHeaders.IsChecked = false;

            this.HtmlHeadersCollapse = HtmlHeadersCollapseType.No;
            App.WorkspaceConfigManager.Set("HtmlHeadersCollapse", this.HtmlHeadersCollapse.ToString());
        }

        /// <summary>
        /// 更改当前工作区目录。
        /// </summary>
        private void btnSetWorkspace_Click(object sender, RoutedEventArgs e)
        {
            ChangeWorkspace();
        }

        #region 搜索资源

        /// <summary>
        /// 在工作区管理器中查找指定的资源文件条目。
        /// </summary>
        private void tbxSearchResource_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
            {
                SearchCustomResources();
                e.Handled = true;
            }
        }

        /// <summary>
        /// 在工作区管理器中查找指定的资源文件条目。
        /// </summary>
        private void SearchCustomResources()
        {
            searchedResourceTreeviewItemList.Clear();
            searchResourceIndex = -1;

            this.cmbSearchResource.Items.Insert(0, cmbSearchResource.Text);

            if (SearchCustomResources(tvWorkDirectory.Items[0] as WorkspaceTreeViewItem, this.cmbSearchResource.Text))
            {
                if (searchedResourceTreeviewItemList.Count >= 0)
                {
                    searchResourceIndex += 1;
                    searchedResourceTreeviewItemList[searchResourceIndex].IsSelected = true;
                    ExpandWorkspaceTreeviewItem(searchedResourceTreeviewItemList[searchResourceIndex]);
                    tvWorkDirectory.Focus();
                }
            }

            if (tcManagerPanels.SelectedItem != tiWorkspace)
                tcManagerPanels.SelectedItem = tiWorkspace;
        }

        /// <summary>
        /// 在工作区管理器中查找指定的资源文件条目。
        /// </summary>
        private bool SearchCustomResources(WorkspaceTreeViewItem item, string findKeyWord)
        {
            if (string.IsNullOrEmpty(findKeyWord)) return false;

            if (item == null) return false;

            bool finded = false;

            if (string.IsNullOrEmpty(item.ShortName)) return false;

            var lowerKeyword = findKeyWord.ToLower();

            if (item.ShortName.ToLower().Contains(lowerKeyword) ||
                item.Title.ToLower().Contains(lowerKeyword))
            {
                searchedResourceTreeviewItemList.Add(item);
                finded = true;
            }

            if (item.Items.Count > 0)
            {
                foreach (var it in item.Items)
                {
                    var subFined = SearchCustomResources(it as WorkspaceTreeViewItem, findKeyWord);
                    finded = subFined || finded;
                }
            }

            return finded;
        }

        private List<WorkspaceTreeViewItem> searchedResourceTreeviewItemList = new List<WorkspaceTreeViewItem>();

        /// <summary>
        /// 用来在切换当前找到的资源时定位。
        /// </summary>
        private int searchResourceIndex = -1;

        /// <summary>
        /// 查找指定的资源文件。
        /// </summary>
        private void btnSearchResource_Clicked(object sender, RoutedEventArgs e)
        {
            SearchCustomResources();
        }

        /// <summary>
        /// 在工作区管理器中选定上一个找到的资源条目。
        /// </summary>
        private void btnSearchPreview_Clicked(object sender, RoutedEventArgs e)
        {
            SelectPreviewSearchedResource();
            e.Handled = true;//避免失焦
        }

        /// <summary>
        /// 在工作区管理器中选定上一个找到的资源条目。
        /// </summary>
        private void SelectPreviewSearchedResource()
        {
            if (searchResourceIndex > 0)
            {
                searchResourceIndex -= 1;
                if (searchResourceIndex >= 0 && searchResourceIndex < searchedResourceTreeviewItemList.Count)
                {
                    searchedResourceTreeviewItemList[searchResourceIndex].IsSelected = true;
                    ExpandWorkspaceTreeviewItem(searchedResourceTreeviewItemList[searchResourceIndex]);
                    tvWorkDirectory.Focus();
                }
            }
        }

        /// <summary>
        /// 在工作区管理器中选定下一个找到的资源条目。
        /// </summary>
        private void btnSearchNext_Clicked(object sender, RoutedEventArgs e)
        {
            SelectNextSearchedResource();
            e.Handled = true;//避免失焦
        }

        /// <summary>
        /// 在工作区管理器中选定下一个找到的资源条目。
        /// </summary>
        private void SelectNextSearchedResource()
        {
            if (searchResourceIndex < searchedResourceTreeviewItemList.Count - 1)
            {
                searchResourceIndex += 1;
                if (searchResourceIndex >= 0 && searchResourceIndex < searchedResourceTreeviewItemList.Count)
                {
                    searchedResourceTreeviewItemList[searchResourceIndex].IsSelected = true;
                    ExpandWorkspaceTreeviewItem(searchedResourceTreeviewItemList[searchResourceIndex]);
                    tvWorkDirectory.Focus();
                }
            }
        }

        /// <summary>
        /// 展开指定的工作区管理器中的某个条目。
        /// </summary>
        /// <param name="item">要展开的某个条目，其所有上级都会被展开。</param>
        private void ExpandWorkspaceTreeviewItem(WorkspaceTreeViewItem item)
        {
            if (item == null) return;

            item.IsExpanded = true;

            var parent = item.ParentWorkspaceTreeViewItem;
            while (parent != null)
            {
                parent.IsExpanded = true;
                parent = parent.ParentWorkspaceTreeViewItem;
            }
        }
        #endregion

        /// <summary>
        /// 在查找框中按回车键执行查找操作。
        /// </summary>
        private void cmbFindText_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
            {
                if (cmbSearchArea.SelectedIndex == 0)
                {
                    btnFind_Click(sender, e);
                }
                else
                {
                    FindText(tvFindAndReplace);
                }
            }
        }

        /// <summary>
        /// 在替换框中按回车键执行替换操作。
        /// </summary>
        private void cmbReplaceTextInputBox_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
            {
                if (cmbSearchArea.SelectedIndex == 0)
                {
                    if ((Keyboard.GetKeyStates(Key.LeftShift) & KeyStates.Down) > 0 ||
                        (Keyboard.GetKeyStates(Key.RightShift) & KeyStates.Down) > 0)
                    {
                        btnReplaceAll_Click(sender, e);
                    }
                    else
                    {
                        btnReplace_Click(sender, e);
                    }
                    e.Handled = true;
                }
            }
        }

        /// <summary>
        /// 根据“查找范围”框中定义的查找范围来查找“查找”框中指定的文本。
        /// </summary>
        /// <param name="treeView">指定将查找到的结果显示在哪个树型框中。</param>
        public void FindText(TreeView treeView)
        {
            treeView.Items.Clear();
            FindText(cmbFindText.Text, (cmbSearchArea.SelectedItem as ComboBoxItem).Tag.ToString(), false, treeView);

            if (treeView.Items.Count <= 0)
            {
                treeView.Items.Add(new TreeViewItem() { Header = "<没找到结果...>", });
            }

            cmbFindText.Items.Add(cmbFindText.Text);

            if (tcRightToolBar.SelectedIndex != 1)
            {
                tcRightToolBar.SelectedItem = tiFindResult;
            }

            if (cdRightToolsArea.ActualWidth < 140)
            {
                cdMainEditArea.Width =
                    cdRightToolsArea.Width = new GridLength(3, GridUnitType.Star);
            }
        }

        /// <summary>
        /// 在磁盘文档或当前正在编辑的文档之间查找指定文本内容。
        /// </summary>
        /// <param name="destText">如果指定了此参数，则按此参数查找。</param>
        /// <param name="preChar">前导字符，决定找到指定的文本后，双击后要选定的文本区域要向前移动到哪个字符。</param>
        /// <param name="nextChar">后缀字符，决定找到指定的文本后，双击后要选定的文本区域要向后移动到哪个字符。</param>
        private void FindText(string keyWord, string searchArea, bool forceRegex, TreeView treeView)
        {
            if (string.IsNullOrEmpty(keyWord)) return;
            try
            {
                if (treeView == null) treeView = tvFindAndReplace;

                treeView.Items.Clear();

                int keywordLength = keyWord.Length;

                var type = FindLineTreeViewItem.ItemType.Normal;
                if (keyWord == "](@" || keyWord == @"\[.*\]\(@.*\)")
                {
                    type = FindLineTreeViewItem.ItemType.Anchor;
                }

                switch (searchArea)
                {
                    case "ActiveDocument":
                        {
                            FindTextInActiveDocument(keyWord, forceRegex, treeView, type);
                            break;
                        }
                    case "OpenedDocuments":
                        {
                            FindTextInOpenedDocuments(keyWord, forceRegex, treeView, type);
                            break;
                        }
                    case "AllFiles":
                        {
                            //这个需要用到递归
                            FindTextInAllFiles(Globals.PathOfWorkspace, keyWord, forceRegex, treeView);
                            break;
                        }
                }
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        /// <summary>
        /// 在打开的所有文档中查找文本。
        /// </summary>
        /// <param name="keyWord">要查找的关键词。</param>
        /// <param name="forceRegex">是否强制使用正则表达式来查找。</param>
        /// <param name="treeView">将查找的结果放在哪个树型框中呈现。</param>
        /// <param name="type">指定要查找什么类型的文本，树型框的条目会呈现为不同的姿态。</param>
        private void FindTextInOpenedDocuments(string keyWord, bool forceRegex, TreeView treeView, FindLineTreeViewItem.ItemType type)
        {
            foreach (var item in this.mainTabControl.Items)
            {
                var efi = item as MarkdownEditor;
                if (efi != null)
                {
                    FindDocumentTreeViewItem fdi = new FindDocumentTreeViewItem(efi.FullFilePath, efi.ShortFileName);
                    Regex regex = GetRegEx(keyWord, false, forceRegex);
                    var matches = regex.Matches(efi.EditorBase.Text);

                    foreach (Match match in matches)
                    {
                        if (match.Success)
                        {
                            var line = efi.EditorBase.Document.GetLineByOffset(match.Index);
                            var lineText = efi.EditorBase.Document.GetText(line.Offset, line.Length);
                            fdi.Items.Add(new FindLineTreeViewItem(efi.FullFilePath, efi.ShortFileName, line.LineNumber,
                                match.Index - line.Offset, match.Length, lineText, null, type));
                        }
                    }

                    if (fdi.Items.Count > 0)
                    {
                        treeView.Items.Add(fdi);
                        fdi.IsExpanded = true;
                        (fdi.Items[0] as TreeViewItem).IsSelected = true;
                    }
                }
            }
        }

        /// <summary>
        /// 在当前活动编辑器中查找文本。
        /// </summary>
        /// <param name="keyWord">要查找的关键词。</param>
        /// <param name="forceRegex">是否强制使用正则表达式来查找。</param>
        /// <param name="treeView">将查找的结果放在哪个树型框中呈现。</param>
        /// <param name="type">指定要查找什么类型的文本，树型框的条目会呈现为不同的姿态。</param>
        private void FindTextInActiveDocument(string keyWord, bool forceRegex, TreeView treeView, FindLineTreeViewItem.ItemType type)
        {
            var efi = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (efi != null)
            {
                FindDocumentTreeViewItem fdi = new FindDocumentTreeViewItem(efi.FullFilePath, efi.ShortFileName);

                Regex regex = GetRegEx(keyWord, false, forceRegex);
                var matches = regex.Matches(efi.EditorBase.Text);

                foreach (Match match in matches)
                {
                    if (match.Success)
                    {
                        var line = efi.EditorBase.Document.GetLineByOffset(match.Index);
                        var lineText = efi.EditorBase.Document.GetText(line.Offset, line.Length);
                        fdi.Items.Add(new FindLineTreeViewItem(efi.FullFilePath, efi.ShortFileName, line.LineNumber,
                            match.Index - line.Offset, match.Length, lineText, null, type));
                    }
                }

                if (fdi.Items.Count > 0)
                {
                    treeView.Items.Add(fdi);
                    fdi.IsExpanded = true;
                    (fdi.Items[0] as TreeViewItem).IsSelected = true;
                }
            }
        }

        /// <summary>
        /// [递归方法]在指定目录下的所有 Markdown 文件中查找指定文本。
        /// </summary>
        /// <param name="directoryPath">基准目录，从该目录开始查找。</param>
        /// <param name="keyWord">查找关键词。</param>
        /// <param name="forceRegex">是否强制使用正则表达式方式查找。</param>
        /// <param name="treeView">将查找结果显示在哪个树型框中。</param>
        private void FindTextInAllFiles(string directoryPath, string keyWord, bool forceRegex, TreeView treeView)
        {
            if (Directory.Exists(directoryPath) == false) return;
            if (string.IsNullOrEmpty(keyWord)) return;
            try
            {
                var keywordLength = keyWord.Length;

                var type = FindLineTreeViewItem.ItemType.Normal;
                if (keyWord == "](@")
                {
                    type = FindLineTreeViewItem.ItemType.Anchor;
                }

                var directory = new DirectoryInfo(directoryPath);

                //先处理当前目录下的文件
                var childFilesInfos = directory.GetFiles();

                foreach (var childFileInfo in childFilesInfos)
                {
                    if (childFileInfo.FullName.ToLower().EndsWith(".md") == false) continue;

                    FindDocumentTreeViewItem fdi = new FindDocumentTreeViewItem(childFileInfo.FullName, childFileInfo.Name);

                    //已打开的文件，按打开的情况算，没打开的，按磁盘文本查找。
                    var fileEditor = GetOpenedEditor(childFileInfo.FullName);
                    if (fileEditor != null)
                    {
                        Regex regex = GetRegEx(keyWord, false, forceRegex);
                        var matches = regex.Matches(fileEditor.EditorBase.Text);

                        foreach (Match match in matches)
                        {
                            if (match.Success)
                            {
                                var line = fileEditor.EditorBase.Document.GetLineByOffset(match.Index);
                                var lineText = fileEditor.EditorBase.Document.GetText(line.Offset, line.Length);
                                fdi.Items.Add(new FindLineTreeViewItem(fileEditor.FullFilePath, fileEditor.ShortFileName, line.LineNumber,
                                    match.Index - line.Offset, match.Length, lineText, null, type));
                            }
                        }
                    }
                    else
                    {
                        Regex regex = GetRegEx(keyWord, false, forceRegex);
                        var fileContent = File.ReadAllText(childFileInfo.FullName);
                        var matches = regex.Matches(fileContent);

                        foreach (Match match in matches)
                        {
                            if (match.Success)
                            {
                                var preLineEndMarkEndIndex = Math.Max(fileContent.LastIndexOf("\r", match.Index), fileContent.LastIndexOf("\n", match.Index));

                                var matchEndIndex = match.Index + match.Length;
                                var nextLineEndStartIndex = Math.Min(fileContent.IndexOf("\r", matchEndIndex), fileContent.IndexOf("\n", matchEndIndex));
                                var lineNumber = 1;
                                for (int i = 0; i < match.Index; i++)
                                {
                                    if (fileContent[i] == '\n') { lineNumber++; }
                                }

                                string lineText;
                                if (nextLineEndStartIndex - preLineEndMarkEndIndex > 0)
                                {
                                    if (preLineEndMarkEndIndex < 0)
                                    {
                                        if (nextLineEndStartIndex < 0)
                                        {
                                            lineText = fileContent;
                                        }
                                        else
                                        {
                                            lineText = fileContent.Substring(0, nextLineEndStartIndex);
                                        }
                                    }
                                    else
                                    {
                                        lineText = fileContent.Substring(preLineEndMarkEndIndex + 1, nextLineEndStartIndex - preLineEndMarkEndIndex - 1);
                                    }
                                }
                                else
                                {
                                    lineText = match.Value;
                                }

                                fdi.Items.Add(new FindLineTreeViewItem(childFileInfo.FullName, childFileInfo.Name, lineNumber,
                                   Math.Max(0, match.Index - preLineEndMarkEndIndex - 1), match.Length, lineText, null, type));
                            }
                        }
                    }

                    if (fdi.Items.Count > 0)
                    {
                        treeView.Items.Add(fdi);
                        fdi.IsExpanded = true;
                    }
                }

                //再递归处理子目录下的文件
                var childDirectories = directory.GetDirectories();
                foreach (var childDirectory in childDirectories)
                {
                    FindTextInAllFiles(childDirectory.FullName, keyWord, forceRegex, treeView);
                }
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        /// <summary>
        /// 根据“查找范围”框中定义的查找范围来查找“查找”框中指定的文本。
        /// </summary>
        private void btnFindText_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            FindText(tvFindAndReplace);
        }

        /// <summary>
        /// 折叠当前活动编辑器中选择区所在的折叠块。
        /// </summary>
        private void miFoldThisBlock_Click(object sender, RoutedEventArgs e)
        {
            var efi = mainTabControl.SelectedItem as MarkdownEditor;
            if (efi == null) return;

            efi.EditorBase.FoldSelectedBlock();
        }

        /// <summary>
        /// 折叠当前活动编辑器中所有可折叠区域。
        /// </summary>
        private void miFoldingAll_Click(object sender, RoutedEventArgs e)
        {
            var efi = mainTabControl.SelectedItem as MarkdownEditor;
            if (efi == null) return;

            foreach (var i in efi.EditorBase.FoldingManager.AllFoldings)
            {
                i.IsFolded = true;
            }
        }

        /// <summary>
        /// 展开当前活动编辑器中所有的折叠区域。
        /// </summary>
        private void miUnFoldingAll_Click(object sender, RoutedEventArgs e)
        {
            var efi = mainTabControl.SelectedItem as MarkdownEditor;
            if (efi == null) return;

            foreach (var i in efi.EditorBase.FoldingManager.AllFoldings)
            {
                i.IsFolded = false;
            }
        }

        /// <summary>
        /// 向指定目录导出当前工作区下所有 Html 文件及其资源文件。
        /// </summary>
        private void miOutportWebSite_Click(object sender, RoutedEventArgs e)
        {
            string destOutportFolder;

            //选择新工作目录
            System.Windows.Forms.FolderBrowserDialog fbd = new System.Windows.Forms.FolderBrowserDialog()
            {
                Description = "　　请选择一个文件夹（目录）作为导出目标。\r\n　　导出时会简单地将除Markdown文件以外的所有文件复制到目标文件夹中。",
                ShowNewFolderButton = true,
            };
            System.Windows.Interop.HwndSource source = PresentationSource.FromVisual(this) as System.Windows.Interop.HwndSource;

            System.Windows.Forms.IWin32Window win = new WinFormWindow(source.Handle);
            System.Windows.Forms.DialogResult result = fbd.ShowDialog(win);
            if (result.Equals(System.Windows.Forms.DialogResult.OK))
            {
                destOutportFolder = fbd.SelectedPath;
            }
            else return;

            OutportFiles(destOutportFolder);
        }

        /// <summary>
        /// 向指定目录导出当前工作区下所有 Html 文件及其资源文件。
        /// </summary>
        /// <param name="destOutportFolder">指定的导出目标目录的绝对路径。</param>
        private void OutportFiles(string destOutportFolder)
        {
            if (Directory.Exists(destOutportFolder) == false)
            {
                LMessageBox.Show("　　指定目录不存在！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            if (destOutportFolder[destOutportFolder.Length - 1] != Path.DirectorySeparatorChar)
            {
                destOutportFolder += Path.DirectorySeparatorChar;
            }

            try
            {
                bool compileBeforeOutport = false;

                var result = LMessageBox.Show("　　导出前要重新编译所有Markdown文件吗？", Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Question);
                if (result == MessageBoxResult.Yes)
                {
                    compileBeforeOutport = true;
                }

                List<FileSystemEntry> fileSystemEntries = new List<FileSystemEntry>();

                CopyCompiledDirectory(Globals.PathOfWorkspace, destOutportFolder, compileBeforeOutport, ref fileSystemEntries);

                //生成一个html索引文档。
                //var indexFilePath = BuildIndexOfCompiledHtmlFiles(fileSystemEntries);   //旧版，根据目录
                var indexFilePath = BuildIndexOfCompiledHtmlFiles();                      //新版，根据工作区管理器布局

                if (File.Exists(destOutportFolder + "_Index.html") == false)
                {
                    File.Copy(indexFilePath, destOutportFolder + "_Index.html");
                }

                // var lines = File.ReadAllLines(Globals.PathOfHistoryWorkspaceFileFullName);

                if (destOutportFolder.EndsWith("\\"))
                {
                    destOutportFolder = destOutportFolder.Substring(0, destOutportFolder.Length - 1);
                }

                destOutportFolder = destOutportFolder.ToLower();

                if (File.Exists(Globals.PathOfHistoryOutputFileFullName))
                {
                    string[] lines = File.ReadAllLines(Globals.PathOfHistoryOutputFileFullName, new UnicodeEncoding());
                    var newLinesList = new List<string>();
                    foreach (var line in lines)
                    {
                        string tmp;
                        if (line.EndsWith("\\"))
                        {
                            tmp = line.Substring(0, line.Length - 1).ToLower();
                        }
                        else tmp = line.ToLower();

                        if (tmp == destOutportFolder) continue;

                        newLinesList.Add(line);
                    }

                    var newLines = new string[newLinesList.Count + 1];
                    newLines[0] = destOutportFolder;
                    for (int i = 1; i < newLines.Length; i++)
                    {
                        newLines[i] = newLinesList[i - 1];
                    }

                    File.WriteAllLines(Globals.PathOfHistoryOutputFileFullName, newLines, new UnicodeEncoding());
                }
                else
                {
                    File.WriteAllLines(Globals.PathOfHistoryOutputFileFullName, new string[] { destOutportFolder }, new UnicodeEncoding());
                }

                for (int i = lbxHistoryOutport.Items.Count - 1; i >= 0; i--)
                {
                    var rwitem = lbxHistoryOutport.Items[i] as RecentDirectoryListBoxItem;
                    if (rwitem != null && rwitem.DirectoryPath == destOutportFolder)
                    {
                        lbxHistoryOutport.Items.Remove(rwitem);
                    }
                }

                var row = new RecentDirectoryListBoxItem(destOutportFolder)
                {
                    ToolTip = "双击将编译的所有 Html 文件及资源文件导出到该目录",
                };
                row.MouseDoubleClick += row_MouseDoubleClick;

                lbxHistoryOutport.Items.Insert(0, row);
                lbxHistoryOutport.SelectedIndex = 0;

                tcManagerPanels.SelectedItem = tiWorkspace;
                RefreshOutputHistoryList();

                //LMessageBox.Show("　　已将工作区目录下所有非Markdown文件（目录）导出到以下磁盘位置：\r\n　　" +
                //    destOutportFolder, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Information);

                System.Diagnostics.Process.Start("explorer.exe", $"\"{destOutportFolder}\"");
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Information);
            }
        }

        /// <summary>
        /// 刷新“历史导出列表”。
        /// </summary>
        private void RefreshOutputHistoryList()
        {
            for (int i = 0; i < lbxHistoryOutport.Items.Count; i++)
            {
                var item = lbxHistoryOutport.Items[i] as RecentDirectoryListBoxItem;
                if (item == null) continue;

                item.IndexText = (i + 1).ToString();
            }
        }


        /// <summary>
        /// 为编译的所有 Html 创建一个带目录结构的 Html 索引文件。
        /// </summary>
        /// <param name="fileSystemEntries">编译过的所有 Html 文件的路径信息。</param>
        /// <returns>返回 Html 索引文件的内容文本。</returns>
        private string BuildIndexOfCompiledHtmlFiles(List<FileSystemEntry> fileSystemEntries)
        {
            if (fileSystemEntries.Count > 0)
            {
                var charSetText = "UTF-8";
                var encoding = Encoding.UTF8;
                if (miGb2312.IsChecked)
                {
                    charSetText = "GB2312";
                    encoding = Encoding.GetEncoding("gb2312");
                }
                StringBuilder sbIndex = new StringBuilder();

                var title = GetMetaFileTitleOfDirectory(Globals.PathOfWorkspace);

                sbIndex.Append("<!DOCTYPE html>\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n<head>\n"
                    + "<meta name=\"viewport\" content=\"width=device-width, user-scalable= yes\"/>\n"
                    + $"<meta http-equiv=\"Content-Type\" content=\"text/html; charset={charSetText}\">\n"
                    + $"<title>{title}</title>\n<link rel=\"stylesheet\" href=\"./lesson_"
                    + Globals.MainWindow.ThemeText + ".css\" type=\"text/css\"></head>\n<body>"
                    + $"<p class='fileheader' id='file_header'>{title}</p><hr />"
                    + "\n");

                int tmpIndex = Globals.PathOfWorkspace.Length;
                string preDirectory = "/";

                foreach (FileSystemEntry fileSystemEntry in fileSystemEntries)
                {
                    string prefix;
                    if (fileSystemEntry.FileFullName.StartsWith("directory_entry:///"))
                    {
                        //目录
                        preDirectory = fileSystemEntry.FileFullName.Substring(19).Substring(Globals.PathOfWorkspace.Length).Replace("\\", "/");
                        if (preDirectory.EndsWith("/") == false) preDirectory += "/";

                        var pieces = preDirectory.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
                        var sb = new StringBuilder();
                        foreach (var p in pieces)
                        {
                            sb.Append(" >> ");
                            sb.Append(FormatDocumentTitle(p));
                        }
                        var preDirectoryText = sb.ToString();
                        prefix = BuildPrefix(preDirectory, true);

                        DirectoryInfo di = new DirectoryInfo(fileSystemEntry.FileFullName.Substring(19));
                        var metaFilePath = (di.FullName.EndsWith("\\") ? di.FullName : (di.FullName + "\\")) + "_" + di.Name + ".html";
                        if (File.Exists(metaFilePath))
                        {
                            FileInfo metaFileInfo = new FileInfo(metaFilePath);

                            string tmpPath2 = di.FullName;
                            if (di.FullName.StartsWith(Globals.PathOfWorkspace))
                            {
                                tmpPath2 = /*destPath + */ di.FullName.Substring(tmpIndex).Replace("\\", "/").Substring(preDirectory.Length - 1);
                                if (tmpPath2.StartsWith("/")) tmpPath2 = tmpPath2.Substring(1);
                            }
                            sbIndex.Append("<p>" + prefix + "<a href=\"./" +
                                (preDirectory == "/" ? "" : preDirectory) + tmpPath2 + metaFileInfo.Name + "\">" + " >> " + GetHtmlFileTile(metaFileInfo) + "</a></p>\n");
                        }
                        else
                        {
                            sbIndex.Append("<p>" + prefix + " >> " + FormatDocumentTitle(di) + " </p>");

                        }
                        continue;
                    }

                    //html文件
                    var fileInfo = new FileInfo(fileSystemEntry.FileFullName);
                    if (fileInfo.Exists == false) continue;

                    if (fileInfo.Name.StartsWith("_") == false && fileInfo.Name.ToLower().EndsWith(".html"))
                    {
                        var fullDirectoryPath = fileInfo.DirectoryName;
                        if (fullDirectoryPath.EndsWith("\\") == false)
                        {
                            fullDirectoryPath += "\\";
                        }

                        if (fullDirectoryPath.StartsWith(Globals.PathOfWorkspace))
                        {
                            preDirectory = fullDirectoryPath.Substring(Globals.PathOfWorkspace.Length).Replace("\\", "/");
                        }
                        else
                        {
                            preDirectory = "";
                        }

                        prefix = BuildPrefix(preDirectory, false);

                        //元文件不在这里添加条目。                       
                        sbIndex.Append("<p>" + prefix + "<a href=\"./" +
                            (preDirectory == "/" ? "" : preDirectory) + fileInfo.Name + "\">" + GetHtmlFileTile(fileInfo) + "</a></p>\n");
                    }
                }

                sbIndex.Append($"<hr /><div class='foot'><p id='compile_time'>{DateTime.Now.ToString()}</p></div></body>\n</html>");

                var indexHtmlFilePath = Globals.PathOfWorkspace + "_index.html";

                File.WriteAllText(indexHtmlFilePath, sbIndex.ToString(), encoding);
                return indexHtmlFilePath;
            }
            else return "";
        }

        /// <summary>
        /// 2017年7月21日新版，改用工作区布局文件来生成，为编译的所有 Html 创建一个带目录结构的 Html 索引文件。
        /// </summary>
        /// <param name="fileSystemEntries">编译过的所有 Html 文件的路径信息。</param>
        /// <returns>返回 Html 索引文件的内容文本。</returns>
        private string BuildIndexOfCompiledHtmlFiles()
        {
            var charSetText = "UTF-8";
            var encoding = Encoding.UTF8;
            if (miGb2312.IsChecked)
            {
                charSetText = "GB2312";
                encoding = Encoding.GetEncoding("gb2312");
            }
            StringBuilder sbIndex = new StringBuilder();

            var title = GetMetaFileTitleOfDirectory(Globals.PathOfWorkspace);

            sbIndex.Append("<!DOCTYPE html>\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n<head>\n"
                + "<meta name=\"viewport\" content=\"width=device-width, user-scalable= yes\"/>\n"
                + $"<meta http-equiv=\"Content-Type\" content=\"text/html; charset={charSetText}\">\n"
                + $"<title>{title}</title>\n<link rel=\"stylesheet\" href=\"./lesson_"
                + Globals.MainWindow.ThemeText + ".css\" type=\"text/css\"></head>\n<body>"
                + $"<p class='fileheader' id='file_header'>{title}</p><hr />"
                + "\n");

            //工作区目录本身的链接应单独添加
            DirectoryInfo diWorkspace = new DirectoryInfo(Globals.PathOfWorkspace);
            if (diWorkspace.Exists)
            {
                var workspaceMetaFileShortName = "_" + diWorkspace.Name + ".html";
                var workspaceMetaFilePath = Globals.PathOfWorkspace + workspaceMetaFileShortName;

                if (File.Exists(workspaceMetaFilePath))
                {
                    var workspaceMetaTitle = GetMetaFileTitleOfDirectory(Globals.PathOfWorkspace);
                    var prefix = "&gt; ";
                    var linkText = $"<p><a href=\"{workspaceMetaFileShortName}\">{prefix}{workspaceMetaTitle}</a></p>\n";
                    sbIndex.Append(linkText);
                }
            }

            BuildWorkspaceIndexFileLinkTexts(Globals.MainWindow.tvWorkDirectory.Items[0] as WorkspaceTreeViewItem, ref sbIndex);

            sbIndex.Append($"<hr /><div class='foot'><p id='compile_time'>{DateTime.Now.ToString()}</p></div></body>\n</html>");

            var indexHtmlFilePath = Globals.PathOfWorkspace + "_index.html";

            File.WriteAllText(indexHtmlFilePath, sbIndex.ToString(), encoding);
            return indexHtmlFilePath;
        }

        /// <summary>
        /// 这是个递归方法。注意：此方法不能为工作区目录本身的目录元文件添加链接！！！
        /// </summary>
        /// <param name="item"></param>
        /// <param name="sbIndex"></param>
        private void BuildWorkspaceIndexFileLinkTexts(WorkspaceTreeViewItem item, ref StringBuilder sbIndex)
        {
            if (item == null) return;
            if (sbIndex == null) return;
            if (Globals.MainWindow.tvWorkDirectory.Items.Count <= 0) return;

            if (item.IsMarkdownFilePath != true && item.IsDirectoryExists != true/*这个条件不能去除！*/) return;
            if (item.FullPath.EndsWith("~") || item.FullPath.EndsWith("~\\")) return;  //资源目录

            if (Globals.MainWindow.IgnoreEncryptedFiles && item.IsEncrypted) return;

            if (Globals.MainWindow.IgnoreAbortedFiles && item.IsAborted) return;

            var level = 1;
            var parentItem = item.ParentWorkspaceTreeViewItem;
            while (parentItem != null)
            {
                if (parentItem == Globals.MainWindow.tvWorkDirectory.Items[0]) break;
                else parentItem = parentItem.ParentWorkspaceTreeViewItem;
                level++;
            }

            var preText = new string('　', level);
            var relativePath = item.RelativeHtmlPath;
            if (string.IsNullOrWhiteSpace(relativePath) != true)
            {
                var prefix = item.IsDirectoryExists ? "&gt; " : "";
                var linkText = $"<p>{preText}<a href=\"{relativePath}\">{prefix}{item.Title}</a></p>\n";
                sbIndex.Append(linkText);
            }
            if (item.Items.Count > 0)
            {
                foreach (WorkspaceTreeViewItem subItem in item.Items)
                {
                    BuildWorkspaceIndexFileLinkTexts(subItem, ref sbIndex);
                }
            }
        }

        /// <summary>
        /// 将字符串开头的数字去除。例如：1－2－3－xxx，格式化后只保留xxx。但如果只剩下空格，就返回原始值。
        /// </summary>
        private string FormatDocumentTitle(DirectoryInfo di)
        {
            return FormatDocumentTitle(di.Name);
        }

        /// <summary>
        /// 取指定 Html 文档中在“<Title></Title>”之中的标题文本。
        /// </summary>
        /// <param name="fileInfo">Html 文件的位置。</param>
        /// <returns>返回 Html 文件的标题。</returns>
        private string GetHtmlFileTile(FileInfo fileInfo)
        {
            if (fileInfo == null || fileInfo.Exists == false) return "";

            //< title > 2016年江苏省普通高等学校招生全国统一考试（江苏卷）（选修科目）说明 </ title >

            Regex regex = new Regex(@"(?<=(<title>)).*(?=(</title>))");

            var encoding = (miUtf8.IsChecked == true) ? Encoding.UTF8 : Encoding.GetEncoding("GB2312");

            using (StreamReader sr = new StreamReader(fileInfo.FullName, encoding))
            {
                var lineText = sr.ReadLine();
                while (lineText != null)
                {
                    var match = regex.Match(lineText);
                    if (match.Success)
                    {
                        return match.Value;
                    }

                    lineText = sr.ReadLine();
                }
            }

            return "";
        }

        /// <summary>
        /// 创建 Html 目录文件时，需要知道各目录之间的层级关系，此关系由前缀决定。
        /// </summary>
        /// <param name="preDirectory">前一目录路径。</param>
        /// <param name="isfolder">是否目录。</param>
        /// <returns>返回前缀字符串，表示层级关系。</returns>
        private string BuildPrefix(string preDirectory, bool isfolder)
        {
            if (string.IsNullOrEmpty(preDirectory)) return string.Empty;
            if (preDirectory == "/" || preDirectory == "\\") return string.Empty;

            StringBuilder sb = new StringBuilder();
            foreach (char c in preDirectory)
            {
                if (c == '/' || c == '\\') sb.Append("　");
            }

            if (isfolder)
            {
                var result = sb.ToString();
                if (preDirectory.EndsWith("/") || preDirectory.EndsWith("\\"))
                {
                    if (result.EndsWith("　")) return result.Substring(0, result.Length - 1);
                    else return result;
                }
                else return result;
            }
            else return sb.ToString();
        }

        /// <summary>
        /// 双击“历史导出目录”列表中某一项，将当前工作区向该项指向的目录导出编译的所有 Html 文件以及资源文件。
        /// </summary>
        void row_MouseDoubleClick(object sender, MouseButtonEventArgs e)
        {
            OutportFiles((sender as RecentDirectoryListBoxItem).DirectoryPath);
        }

        /// <summary>
        /// 编译所有指定的 Markdown 文件。
        /// </summary>
        /// <param name="compiledFiles">用于返回被编译的 Markdown 文件的信息，这样可用于生成目录 Html 文件。</param>
        public static bool CompileAllMdFiles(ref List<FileSystemEntry> compiledFiles)
        {
            //旧版，不好。
            //CopyCompiledDirectory(Globals.PathOfWorkspace, "", true, ref compiledFiles);

            if (Globals.MainWindow.tvWorkDirectory.Items.Count <= 0) return false;

            var rootItem = Globals.MainWindow.tvWorkDirectory.Items[0] as WorkspaceTreeViewItem; ;
            if (rootItem == null) return false;

            CopyCompiledDirectory(rootItem, "", true, ref compiledFiles);

            return compiledFiles.Count > 0;
        }

        /// <summary>
        /// 递归获取指定工作区条目项下所有有效的 Markdown 文件路径（含目录元文件）。
        /// </summary>
        /// <param name="wtvi"></param>
        /// <param name="filePathList"></param>
        /// <returns></returns>
        private static void GetMarkdownFilePathsFromWorkspaceItem(WorkspaceTreeViewItem wtvi, ref List<string> filePathList)
        {
            if (wtvi == null || filePathList == null) return;

            if (wtvi.IsMarkdownFilePath)
            {
                if (Globals.MainWindow.IgnoreEncryptedFiles)
                {
                    //编译工作区时忽略已加密文件
                    string password, passwordTip;
                    var isEncrypted = CustomMarkdownSupport.IsFileEncrypted(wtvi.FullPath, out password, out passwordTip);
                    if (isEncrypted) return;//忽略被加密的文档。
                }

                if (Globals.MainWindow.IgnoreAbortedFiles)
                {
                    //编译工作区时忽略被标记为“废弃”的文件。
                    var isAbortedFile = CustomMarkdownSupport.IsFileAborted(wtvi.FullPath);
                    if (isAbortedFile) return;
                }

                filePathList.Add(wtvi.FullPath);
            }
            else if (wtvi.IsMetaDirectoryPath)
            {
                var metaFilePath = wtvi.MetaFilePath;
                if (Globals.MainWindow.IgnoreEncryptedFiles)
                {
                    //编译工作区时忽略已加密文件
                    string password, passwordTip;
                    var isEncrypted = CustomMarkdownSupport.IsFileEncrypted(metaFilePath, out password, out passwordTip);
                    if (isEncrypted) return;//忽略被加密的文档。
                }

                if (Globals.MainWindow.IgnoreAbortedFiles)
                {
                    //编译工作区时忽略被标记为“废弃”的文件。
                    var isAbortedFile = CustomMarkdownSupport.IsFileAborted(metaFilePath);
                    if (isAbortedFile) return;
                }

                filePathList.Add(wtvi.MetaFilePath);
                if (wtvi.Items.Count > 0)
                {
                    foreach (var subItem in wtvi.Items)
                    {
                        var subWtvi = subItem as WorkspaceTreeViewItem;
                        GetMarkdownFilePathsFromWorkspaceItem(subWtvi, ref filePathList);
                    }
                }
            }
        }

        /// <summary>
        /// 2017年7月23日，新版，按工作区管理器布局编译Html文件，不好。
        /// 编译指定目录下（含下级）所有 Markdown 文件，并将编译好的 Html 文件按目录结构输出到指定的另一个目录中。
        /// </summary>
        /// <param name="srcDirectory">Markdown 文件所在的源目录的路径。</param>
        /// <param name="destDirectory">传入无效的路径可以避免复制，这样就只是将 Markdown 文件编译为 Html 文件了。编译的 Html 文件与 Markdown 文件在同一目录下。</param>
        /// <param name="compileMarkdownFilesBeforeCopy">在向指定目录复制编译生成的 Html 文件之前是否先重新编译一下。</param>
        /// <param name="fileSystemEntries">此参数用于生成目录索引 Html 文件。</param>
        public static void CopyCompiledDirectory(WorkspaceTreeViewItem wtvi, string destDirectory, bool compileMarkdownFilesBeforeCopy, ref List<FileSystemEntry> fileSystemEntries)
        {
            if (string.IsNullOrEmpty(destDirectory) == false && destDirectory[destDirectory.Length - 1] != Path.DirectorySeparatorChar)
            {
                destDirectory += Path.DirectorySeparatorChar;
            }

            List<string> filePathList = new List<string>();
            GetMarkdownFilePathsFromWorkspaceItem(wtvi, ref filePathList);

            if (compileMarkdownFilesBeforeCopy)
            {
                for (int i = 0; i < filePathList.Count; i++)
                {
                    string mdFilePath = filePathList[i];

                    //必须分两次循环，先考虑编译，再复制
                    if (File.Exists(mdFilePath))
                    {
                        var srcFilePath = mdFilePath;// srcDirectory + Path.GetFileName(fileEntry);
                        if (srcFilePath.ToLower().EndsWith(".md"))
                        {
                            string htmlTitle = "";

                            if (Globals.MainWindow.IgnoreEncryptedFiles)
                            {
                                //编译工作区时忽略已加密文件
                                string password, passwordTip;
                                var isEncrypted = CustomMarkdownSupport.IsFileEncrypted(srcFilePath, out password, out passwordTip);
                                if (isEncrypted) continue;//忽略被加密的文档。
                            }

                            if (Globals.MainWindow.IgnoreAbortedFiles)
                            {
                                //编译工作区时忽略被标记为“废弃”的文件。
                                var isAbortedFile = CustomMarkdownSupport.IsFileAborted(srcFilePath);
                            }

                            string previewPageLink = "";
                            if (i >= 1)
                            {
                                previewPageLink = $"<a href=\"{MarkDownEditorBase.GetRelativePath(srcFilePath, filePathList[i - 1], true)}\">&lt; 前页</a>";
                            }

                            string nextPageLink = "";
                            if (i < filePathList.Count - 1)
                            {
                                nextPageLink = $"<a href=\"{MarkDownEditorBase.GetRelativePath(srcFilePath, filePathList[i + 1], true)}\">后页 &gt;</a>";
                            }

                            var filePath = CustomMarkdownSupport.Compile(srcFilePath,
                                        Globals.MainWindow.HtmlHeadersCollapse,
                                        null, 1, ref htmlTitle, true, false, 1, previewPageLink, nextPageLink);
                            if (filePath == null) continue;
                        }
                    }
                }
            }

            foreach (string entry in filePathList)
            {
                if (Directory.Exists(entry))
                {
                    if (entry.EndsWith("~") == false && entry.EndsWith("~" + Path.DirectorySeparatorChar) == false)
                    {
                        fileSystemEntries.Add(new FileSystemEntry()
                        {
                            FileFullName = "directory_entry:///" + entry,
                            Title = "",
                        });
                    }

                    if (string.IsNullOrWhiteSpace(destDirectory))
                    {
                        CopyCompiledDirectory(entry, "", compileMarkdownFilesBeforeCopy, ref fileSystemEntries);
                    }
                    else
                    {
                        CopyCompiledDirectory(entry, destDirectory + Path.GetFileName(entry), compileMarkdownFilesBeforeCopy, ref fileSystemEntries);
                    }
                }
                else
                {
                    if (entry.ToLower().EndsWith(".md") && File.Exists(entry) &&
                        entry.ToLower().EndsWith("~.md") == false)
                    {
                        var fileInfo = new FileInfo(entry.Substring(0, entry.Length - 3) + ".html");
                        if (fileInfo.Name.StartsWith("_") == false)
                        {
                            fileSystemEntries.Add(
                                new FileSystemEntry()
                                {
                                    FileFullName = fileInfo.FullName,
                                    Title = fileInfo.Name,
                                });
                        }
                    }

                    var destFileFullPath = destDirectory + Path.GetFileName(entry);
                    if (string.IsNullOrWhiteSpace(destDirectory) == false)//为空字符串时不复制。
                    {
                        if (Directory.Exists(destDirectory) == false && string.IsNullOrWhiteSpace(destDirectory) == false)
                        {
                            try
                            {
                                Directory.CreateDirectory(destDirectory);

                                if (destFileFullPath.ToLower().EndsWith(".md") == false && File.Exists(destFileFullPath) == false)
                                {
                                    System.IO.File.Copy(entry, destFileFullPath, true);
                                }
                            }
                            catch (Exception ex)
                            {
                                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace);
                            }
                        }
                        else
                        {

                            if (destFileFullPath.ToLower().EndsWith(".md") == false && File.Exists(destFileFullPath) == false)
                            {
                                System.IO.File.Copy(entry, destFileFullPath, true);
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// 旧版，按工作区目录编译Html文件，不好。
        /// 编译指定目录下（含下级）所有 Markdown 文件，并将编译好的 Html 文件按目录结构输出到指定的另一个目录中。
        /// </summary>
        /// <param name="srcDirectory">Markdown 文件所在的源目录的路径。</param>
        /// <param name="destDirectory">传入无效的路径可以避免复制，这样就只是将 Markdown 文件编译为 Html 文件了。编译的 Html 文件与 Markdown 文件在同一目录下。</param>
        /// <param name="compileMarkdownFilesBeforeCopy">在向指定目录复制编译生成的 Html 文件之前是否先重新编译一下。</param>
        /// <param name="fileSystemEntries">此参数用于生成目录索引 Html 文件。</param>
        public static void CopyCompiledDirectory(string srcDirectory, string destDirectory, bool compileMarkdownFilesBeforeCopy, ref List<FileSystemEntry> fileSystemEntries)
        {
            String[] Entries;

            if (string.IsNullOrEmpty(destDirectory) == false && destDirectory[destDirectory.Length - 1] != Path.DirectorySeparatorChar)
            {
                destDirectory += Path.DirectorySeparatorChar;
            }

            //不能用下面这段代码，因为destDirectory完全可能不是合法的目录路径，这样会引发异常。
            #region 废弃代码
            //if (string.IsNullOrEmpty(destDirectory) == false && !Directory.Exists(destDirectory))
            //{
            //    try
            //    {
            //        Directory.CreateDirectory(destDirectory);
            //    }
            //    catch (Exception ex)
            //    {
            //        LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace);
            //        return;
            //    }
            //} 
            #endregion

            Entries = Directory.GetFileSystemEntries(srcDirectory);

            DirectoryInfo di = new DirectoryInfo(srcDirectory);
            string mataFileEntry = null;
            var diName = (di.Name.EndsWith("\\") || di.Name.EndsWith("/")) ? di.Name.Substring(0, di.Name.Length - 1).ToLower() : di.Name.ToLower();
            List<string> entryList = new List<string>();

            for (int i = 0; i < Entries.Length; i++)
            {
                if (Entries[i].ToLower().EndsWith($"_{diName}.md"))
                {
                    mataFileEntry = Entries[i];
                }
                else
                {
                    entryList.Add(Entries[i]);
                }
            }

            entryList.Sort(new FileSystemEntriesCompare());

            if (mataFileEntry != null)
            {
                entryList.Insert(0, mataFileEntry);
            }

            if (compileMarkdownFilesBeforeCopy)
            {
                foreach (string fileEntry in entryList)
                {
                    //必须分两次循环，先考虑编译，再复制
                    if (File.Exists(fileEntry))
                    {
                        var srcFilePath = fileEntry;// srcDirectory + Path.GetFileName(fileEntry);
                        if (srcFilePath.ToLower().EndsWith(".md"))
                        {
                            string htmlTitle = "";

                            if (Globals.MainWindow.IgnoreEncryptedFiles)
                            {
                                //编译工作区时忽略已加密文件
                                string password, passwordTip;
                                var isEncrypted = CustomMarkdownSupport.IsFileEncrypted(srcFilePath, out password, out passwordTip);
                                if (isEncrypted) continue;//忽略被加密的文档。
                            }

                            if (Globals.MainWindow.IgnoreAbortedFiles)
                            {
                                //编译工作区时忽略被标记为“废弃”的文件。
                                var isAbortedFile = CustomMarkdownSupport.IsFileAborted(srcFilePath);
                            }

                            var filePath = CustomMarkdownSupport.Compile(srcFilePath,
                                        Globals.MainWindow.HtmlHeadersCollapse,
                                        null, 1, ref htmlTitle, true);
                            if (filePath == null) continue;
                        }
                    }
                }
            }

            foreach (string entry in entryList)
            {
                if (Directory.Exists(entry))
                {
                    if (entry.EndsWith("~") == false && entry.EndsWith("~" + Path.DirectorySeparatorChar) == false)
                    {
                        fileSystemEntries.Add(new FileSystemEntry()
                        {
                            FileFullName = "directory_entry:///" + entry,
                            Title = "",
                        });
                    }

                    if (string.IsNullOrWhiteSpace(destDirectory))
                    {
                        CopyCompiledDirectory(entry, "", compileMarkdownFilesBeforeCopy, ref fileSystemEntries);
                    }
                    else
                    {
                        CopyCompiledDirectory(entry, destDirectory + Path.GetFileName(entry), compileMarkdownFilesBeforeCopy, ref fileSystemEntries);
                    }
                }
                else
                {
                    if (entry.ToLower().EndsWith(".md") && File.Exists(entry) &&
                        entry.ToLower().EndsWith("~.md") == false)
                    {
                        var fileInfo = new FileInfo(entry.Substring(0, entry.Length - 3) + ".html");
                        if (fileInfo.Name.StartsWith("_") == false)
                        {
                            fileSystemEntries.Add(
                                new FileSystemEntry()
                                {
                                    FileFullName = fileInfo.FullName,
                                    Title = fileInfo.Name,
                                });
                        }
                    }

                    var destFileFullPath = destDirectory + Path.GetFileName(entry);
                    if (string.IsNullOrWhiteSpace(destDirectory) == false)//为空字符串时不复制。
                    {
                        if (Directory.Exists(destDirectory) == false && string.IsNullOrWhiteSpace(destDirectory) == false)
                        {
                            try
                            {
                                Directory.CreateDirectory(destDirectory);

                                if (destFileFullPath.ToLower().EndsWith(".md") == false && File.Exists(destFileFullPath) == false)
                                {
                                    System.IO.File.Copy(entry, destFileFullPath, true);
                                }
                            }
                            catch (Exception ex)
                            {
                                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace);
                            }
                        }
                        else
                        {

                            if (destFileFullPath.ToLower().EndsWith(".md") == false && File.Exists(destFileFullPath) == false)
                            {
                                System.IO.File.Copy(entry, destFileFullPath, true);
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// 编译当前活动编辑器，并预览之。
        /// </summary>
        private void btnCompileAndPreviewHtml_MouseLeftButtonDown(object sender, RoutedEventArgs e)
        {
            CompileAndPreviewHtml(true);
        }

        /// <summary>
        /// 插入 Html 格式的水平线标记文本。
        /// </summary>
        private void miInsertHorizontalLine_Click(object sender, RoutedEventArgs e)
        {
            MenuItem mi = sender as MenuItem;
            if (mi == null) return;

            var value = int.Parse(mi.Tag as string);

            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            if (value == 0)
            {
                eti.EditorBase.SelectedText = "\r\n\r\n----------------------------------------\r\n\r\n";
            }
            else
            {
                eti.EditorBase.SelectedText = "<hr class=\"hr" + value + "\"/>\r\n";
            }

            var destSel = eti.EditorBase.SelectionStart + eti.EditorBase.SelectionLength;
            if (destSel < eti.EditorBase.Document.TextLength)
            {
                eti.EditorBase.Select(destSel, 0);
            }
        }

        /// <summary>
        /// 用一对方括弧将选定文本片段括起来。形如：【原文本】。
        /// </summary>
        private void miWrapWithSquareQuoters_Click(object sender, RoutedEventArgs e)
        {
            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;
            eti.EditorBase.WrapTextWithSquareQuotes();
        }

        /// <summary>
        /// 插入一个选择题相关标记。
        /// </summary>
        private void miInsertChoiceQuestion_Click(object sender, RoutedEventArgs e)
        {
            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;
            eti.EditorBase.InsertChoiceQuestionTags();
        }

        /// <summary>
        /// 插入一个判断题相关标记。
        /// </summary>
        private void miInsertJudgeQuestion_Click(object sender, RoutedEventArgs e)
        {
            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;
            eti.EditorBase.InsertJudgeQuestionTags((sender as MenuItem).Tag.ToString());
        }

        /// <summary>
        /// 检验当前活动编辑器中的试题是否合法（不合法的是指格式不全，不能用于演示。）
        /// </summary>
        private void miValidateTestPaper_Click(object sender, RoutedEventArgs e)
        {
            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            StringBuilder errorMsg = new StringBuilder();
            if (QuestionValidateManager.ValidateAsTestPaper(OutLineExamsText(eti.EditorBase.Document.Text), errorMsg))
            {
                tbxValidatedInfo.Text = "文件通过合法性验证，可以使用。\r\n==========√√√√√==========\r\n";
            }
            else
            {
                tbxValidatedInfo.Text = "文件未通过合法性验证，错误消息如下：\r\n============××××××============\r\n" + errorMsg.ToString();
            }

            if (tcRightToolBar.SelectedItem != tabValidateDocument)
            {
                tcRightToolBar.SelectedItem = tabValidateDocument;
            }
        }

        /// <summary>
        /// 演示当前活动编辑器中所有试题。
        /// </summary>
        private void miPresentation_Click(object sender, RoutedEventArgs e)
        {
            PresentationExams();
        }

        /// <summary>
        /// 抽取所有与试题相关的文本行。
        /// </summary>
        /// <param name="src">源文本。</param>
        /// <returns>返回源文本中与试题相关的所有文本行。</returns>
        private string OutLineExamsText(string src)
        {
            if (string.IsNullOrEmpty(src)) return "";

            var lines = src.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
            StringBuilder sb = new StringBuilder();
            foreach (var l in lines)
            {
                if (Question.IsExamTextLine(l))
                {
                    sb.Append(l);
                    sb.Append("\r\n");
                }
            }

            return sb.ToString();
        }

        /// <summary>
        /// 演示当前活动编辑器中的试题（如果有）。
        /// </summary>
        public void PresentationExams()
        {
            var edit = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (edit == null)
            {
                LMessageBox.Show("没有文档可以显示！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            var examsText = OutLineExamsText(edit.EditorBase.Document.Text);

            StringBuilder errorMsg = new StringBuilder();
            if (QuestionValidateManager.ValidateForPresentation(examsText, errorMsg))//这包括了填空题。
            {
                tbxValidatedInfo.Text = "文件通过合法性验证，可以使用。\r\n========√√√√========\r\n";
            }
            else
            {
                tbxValidatedInfo.Text = "文件未通过合法性验证，错误消息如下：\r\n=========××××========\r\n" + errorMsg.ToString();

                if (tcRightToolBar.SelectedItem != tabValidateDocument)
                {
                    tcRightToolBar.SelectedItem = tabValidateDocument;
                }

                LMessageBox.Show("　　此文档中无试题或试题有格式错误，无法演示！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);

                return;
            }

            //这句不能少，否则头一次显示不出任何试题。
            edit.PresentationWindow.BuildQuestions(examsText);
            edit.PresentationWindow.ShowDialog();
        }

        /// <summary>
        /// Html 预览区 TabItem 标头双击编译当前活动编辑器并预览。
        /// </summary>
        private void TextBlock_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (e.ClickCount == 2)
            {
                CompileAndPreviewHtml(true);
            }
        }

        /// <summary>
        /// Html 预览区 TabItem 标头变色效果。
        /// </summary>
        private void StackPanel_MouseEnter(object sender, MouseEventArgs e)
        {
            (sender as StackPanel).Background = Brushes.LightGray;
        }

        /// <summary>
        /// Html 预览区 TabItem 标头变色效果。
        /// </summary>
        private void StackPanel_MouseLeave(object sender, MouseEventArgs e)
        {
            (sender as StackPanel).Background = Brushes.Transparent;
        }

        /// <summary>
        /// 创建试题演示界面所需要的提问名册。演示试题时，按特定快捷键就可以弹出一个学生名字提问。
        /// </summary>
        private void miCreateStudentsList_Click(object sender, RoutedEventArgs e)
        {
            var inputBoxResult = InputBox.Show(Globals.AppName, "　　请输入班级名：", "", false);

            if (string.IsNullOrEmpty(inputBoxResult)) return;

            try
            {
                string studentListDirectoryPath = Globals.PathOfUserFolder + (Globals.PathOfUserFolder.EndsWith("\\") ? "" : "\\") + "StudentsLists\\";
                if (Directory.Exists(studentListDirectoryPath) == false)
                {
                    Directory.CreateDirectory(studentListDirectoryPath);
                }

                DirectoryInfo studentListDirectoryinfo = new DirectoryInfo(studentListDirectoryPath);

                var newStudentsListFilePath = studentListDirectoryPath + (inputBoxResult.EndsWith(".txt") ? inputBoxResult : (inputBoxResult + ".txt"));
                if (File.Exists(newStudentsListFilePath) == false)
                {
                    //或不存在同名文件，就创建一个。
                    File.WriteAllText(newStudentsListFilePath, Properties.Resources.SampleClassFileContent);
                }

                //编辑文件
                var sw = new PlainTextEditor(newStudentsListFilePath)
                {
                    Owner = this,
                    WindowStartupLocation = WindowStartupLocation.CenterOwner,
                    Title = Globals.AppName + " - 创建班级名册",
                };

                if (File.Exists(newStudentsListFilePath))
                {
                    sw.tbx.Text = File.ReadAllText(newStudentsListFilePath);
                }

                if (string.IsNullOrEmpty(sw.tbx.Text))
                {
                    sw.tbx.Text = Properties.Resources.SampleClassFileContent;
                }

                if (sw.ShowDialog() == true)
                {
                    LMessageBox.Show("　　新的班级名册在重新启动后才会生效！", Globals.AppName, MessageBoxButton.OK,
                         MessageBoxImage.Information);
                }
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace);
            }
        }

        /// <summary>
        /// 切换“IsExamEnabled”开关。此开关打开时才能使用试题编辑、演示功能。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void miIsExamEnabled_Click(object sender, RoutedEventArgs e)
        {
            if (this.IsExamEnabled == false && this.IsAutoCompletionEnabled == false)
            {
                var result = LMessageBox.Show("需要打开【自动完成】，要继续吗？", Globals.AppName,
                     MessageBoxButton.YesNo, MessageBoxImage.Question);
                if (result != MessageBoxResult.Yes) return;

                miIsAutoCompletionEnabled.IsChecked = true;
                this.IsAutoCompletionEnabled = true;
                App.ConfigManager.Set("IsAutoCompletionEnabled", true.ToString());
            }

            miIsExamEnabled.IsChecked = !miIsExamEnabled.IsChecked;
            this.IsExamEnabled = miIsExamEnabled.IsChecked;
            App.ConfigManager.Set("IsExamEnabled", miIsExamEnabled.IsChecked.ToString());
        }

        /// <summary>
        /// 切换“IsAutoCompletionEnabled”属性的开关。此开关打开时才能使用“自动完成”功能。
        /// </summary>
        private void miIsAutoCompletionEnabled_Clicked(object sender, RoutedEventArgs e)
        {
            miIsAutoCompletionEnabled.IsChecked = !miIsAutoCompletionEnabled.IsChecked;
            this.IsAutoCompletionEnabled = miIsAutoCompletionEnabled.IsChecked;
            App.ConfigManager.Set("IsAutoCompletionEnabled", miIsAutoCompletionEnabled.IsChecked.ToString());

            if (this.IsAutoCompletionEnabled == false)
            {
                if (this.IsExamEnabled)
                {
                    this.miIsExamEnabled.IsChecked = this.IsExamEnabled = false;
                    App.ConfigManager.Set("IsExamEnabled", false.ToString());
                }

                if (this.IsEnToChineseDictEnabled)
                {
                    this.miIsEnToChineseDictEnabled.IsChecked = this.IsEnToChineseDictEnabled = false;
                    App.ConfigManager.Set("IsEnToChineseDictEnabled", false.ToString());
                }
            }
        }

        /// <summary>
        /// 双击左下角图像预览区，选定当前预览的图像文件在工作区管理器中的对应条目。
        /// </summary>
        private void imagePreviewOutBorder_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (e.ClickCount == 2)
            {
                var wti = (sender as Border).Tag as WorkspaceTreeViewItem;
                if (wti == null) return;

                wti.IsSelected = true;
            }
        }

        /// <summary>
        /// 根据“查找范围”框的限制，在指定范围的 Markdown 文件中查找所有的“锚”。
        /// </summary>
        private void btnFindAnchors_MouseLeftButtonDown(object sender, RoutedEventArgs e)
        {
            //锚的定义方式：
            //[锚名](@锚ID)
            //锚名可以省略，但锚ID不能省略。
            //编译后，会变成这样：
            //<a id="锚ID">锚名</a>
            string keyWord = @"\[.*\]\(@.*\)";

            FindText(keyWord, (cmbSearchArea.SelectedItem as ComboBoxItem).Tag.ToString(), true, tvFindAndReplace);

            if (tcRightToolBar.SelectedItem != tiFindResult)
            {
                tcRightToolBar.SelectedItem = tiFindResult;
            }

            if (cdRightToolsArea.ActualWidth < 140)
            {
                cdMainEditArea.Width =
                    cdRightToolsArea.Width = new GridLength(3, GridUnitType.Star);
            }
        }

        /// <summary>
        /// 返回当前活动的 Markdown 编辑器。
        /// </summary>
        public MarkdownEditor ActivedEditor
        {
            get
            {
                if (this.mainTabControl.Items.Count <= 0) return null;

                return this.mainTabControl.SelectedItem as MarkdownEditor;
            }
        }

        /// <summary>
        /// 启动时折叠左工具栏。
        /// </summary>
        public bool StartCollapseLeftTools { get; private set; }

        /// <summary>
        /// 启动时折叠右工具栏。
        /// </summary>
        public bool StartCollapseRightTools { get; private set; }

        /// <summary>
        /// 启动时显示工作区管理器。
        /// </summary>
        public bool SelectWorkspaceAtStart { get; private set; } = true;

        /// <summary>
        /// 在当前文档中查找对工作区管理器中选定条目的引用。
        /// </summary>
        private void miFindRefInActiveDocument_Click(object sender, RoutedEventArgs e)
        {
            FindRef("ActiveDocument");
        }

        /// <summary>
        /// 根据“查找范围”查找所有对当前工作区管理器中选定条目的引用。
        /// </summary>
        /// <param name="findArea">指定查找的范围，包括：“ActiveDocument”、“OpenedDocuments”、“AllFiles”之一。</param>
        private void FindRef(string findArea)
        {
            var wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (wtvi == null) return;

            var shortName = wtvi.ShortName;
            if (shortName.ToLower().EndsWith(".md"))
            {
                shortName = shortName.Substring(0, shortName.Length - 3);
            }

            if (string.IsNullOrEmpty(shortName)) return;

            FindText(shortName, findArea, false, tvFindAndReplace);

            tcRightToolBar.SelectedItem = tiFindResult;
        }

        /// <summary>
        /// 查找所有打开的文档中对当前条目的引用。
        /// </summary>
        private void miFindRefInOpenedDocument_Click(object sender, RoutedEventArgs e)
        {
            FindRef("OpenedDocuments");
        }

        /// <summary>
        /// 按工作区管理器中选定条目的名称查找工作区（及其下级目录）中所有 Markdown 文件中对此文件（或目录）的引用。
        /// 这通常用于改变目录（或文件）的名称前，以防止这些引用失效。
        /// 一般情况下，不应该更改目录（或文件）的名称，以防止引用失效。
        /// 如果有排序的需要，应该在创建时给文件（或目录）赋予数字开头的名称，且数字不应连贯。
        /// </summary>
        private void miFindRefInDiskFiles_Click(object sender, RoutedEventArgs e)
        {
            bool someFileIsModified = false;
            foreach (var i in this.mainTabControl.Items)
            {
                MarkdownEditor me = i as MarkdownEditor;
                if (me == null) continue;

                if (me.IsModified)
                {
                    someFileIsModified = true;
                    break;
                }
            }

            if (someFileIsModified)
            {
                var result = LMessageBox.Show("　　这个功能只管从磁盘上查找，而不管当前正在编辑的内容，所以查找的结果未必准确。\r\n" +
                    "　　如果要保证查找结果准确，请先保存这些文档或者使用“查找打开的文档”、“查找当前文档”这两个菜单条目！\r\n\r\n　　要继续吗？",
                    Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Question);
                if (result != MessageBoxResult.Yes) return;
            }

            FindRef("AllFiles");
        }

        /// <summary>
        /// 给当前活动编辑器中选中的文本添加倾斜标记（不能跨行）。
        /// </summary>
        private void miItalic_Click(object sender, RoutedEventArgs e)
        {
            WrapWithItalic();
        }

        /// <summary>
        /// 给当前活动编辑器中选中的文本添加加粗标记（不能跨行）。
        /// </summary>
        private void miBold_Click(object sender, RoutedEventArgs e)
        {
            WrapWithBold();
        }

        /// <summary>
        /// 给当前活动编辑器中选中的文本添加删除线标记（不能跨行）。
        /// </summary>
        private void miUnderLine_Click(object sender, RoutedEventArgs e)
        {
            WrapWithUTag();
        }

        /// <summary>
        /// 给当前活动编辑器中选中的文本添加下划线标记（不能跨行）。
        /// </summary>
        private void miStrikeLine_Click(object sender, RoutedEventArgs e)
        {
            WrapWithSTag();
        }

        private void miSelectWorkspaceAtStart_Click(object sender, RoutedEventArgs e)
        {
            miSelectWorkspaceAtStart.IsChecked = !miSelectWorkspaceAtStart.IsChecked;
            SelectWorkspaceAtStart = miSelectWorkspaceAtStart.IsChecked;
            App.ConfigManager.Set("SelectWorkspaceAtStart", miSelectWorkspaceAtStart.IsChecked.ToString());
        }

        /// <summary>
        /// 切换全屏状态。
        /// </summary>
        private void miFullScreen_Click(object sender, RoutedEventArgs e)
        {
            cmbPerspective.SelectedIndex = (int)Perspective.EditingAndPreview;
        }

        private void miFullScreenEditArea_Click(object sender, RoutedEventArgs e)
        {
            cmbPerspective.SelectedIndex = (int)Perspective.FullScreenEditing;
        }

        /// <summary>
        /// 全屏预览。
        /// </summary>
        private void miPreviewFullScreen_Click(object sender, RoutedEventArgs e)
        {
            cmbPerspective.SelectedIndex = (int)Perspective.FullScreenPreview;
        }

        /// <summary>
        /// 全屏。
        /// </summary>
        private void btnFullScreen_Click(object sender, RoutedEventArgs e)
        {
            cmbPerspective.SelectedIndex = (int)Perspective.EditingAndPreview;
        }

        /// <summary>
        /// 旧版的自定义最小化窗口按钮。
        /// </summary>
        private void btnMinize_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            this.WindowState = WindowState.Minimized;
        }

        /// <summary>
        /// 旧版的自定义恢复窗口按钮。
        /// </summary>
        private void btnRestore_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (this.WindowState == WindowState.Normal)
            {
                this.WindowState = WindowState.Maximized;
            }
            else if (this.WindowState == WindowState.Maximized)
            {
                this.WindowState = WindowState.Normal;
            }
        }

        /// <summary>
        /// 旧版的自定义关闭窗口按钮。
        /// </summary>
        private void btnClose_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            this.Close();
        }

        /// <summary>
        /// 旧版的自定义窗口最大化、最小化、恢复图标。
        /// </summary>
        private BitmapImage restoreImageSource = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/restore.png"));
        /// <summary>
        /// 旧版的自定义窗口最大化、最小化、恢复图标。
        /// </summary>
        private BitmapImage maxsizeImageSource = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/maxsize.png"));

        /// <summary>
        /// [用于调试]输出指定控件的模板文本。仅用于调试。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void miWriteControlTemplate_Click(object sender, RoutedEventArgs e)
        {
            string templateText = GetTemplateXamlCode(mainTabControl.SelectedItem as Control);
            File.WriteAllText("D:\\控件模板.txt", templateText);
            LMessageBox.Show("控件模板文件已输出到：D:\\控件模板.txt");
        }

        /// <summary>
        /// [用于调试]输出指定控件的模板文本。仅用于调试。
        /// </summary>
        /// <param name="ctrl"></param>
        /// <returns></returns>
        string GetTemplateXamlCode(Control ctrl)
        {
            FrameworkTemplate template = ctrl.Template;
            string xaml = "";
            if (template != null)
            {
                XmlWriterSettings settings = new XmlWriterSettings();
                settings.Indent = true;
                settings.IndentChars = new string(' ', 4);
                settings.NewLineOnAttributes = true;
                StringBuilder strbuild = new StringBuilder();
                XmlWriter xmlwrite = XmlWriter.Create(strbuild, settings);
                try
                {
                    XamlWriter.Save(template, xmlwrite);
                    xaml = strbuild.ToString();
                }
                catch (Exception exc)
                {
                    xaml = exc.Message;
                }
            }
            else
            {
                xaml = "no template";
            }
            return xaml;
        }

        /// <summary>
        /// 旧的自定义主窗口标题栏，已无必要存在。
        /// </summary>
        private void dpTitle_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (e.LeftButton == MouseButtonState.Pressed)
            {
                this.DragMove();
            }

            if (e.ClickCount == 2)
            {
                if (this.WindowState == WindowState.Maximized)
                {
                    this.WindowState = WindowState.Normal;
                }
                else if (this.WindowState == WindowState.Normal)
                {
                    this.WindowState = WindowState.Maximized;
                }
            }
        }

        private string defaultEncoding = "utf-8";
        /// <summary>
        /// 下次编译 html 时使用的默认编码。
        /// </summary>
        public string DefaultEncoding
        {
            get
            {
                return defaultEncoding;
            }
        }

        /// <summary>
        /// 设置下次编译 Html 时使用 UTF-8 编码。
        /// </summary>
        private void miUtf8_Click(object sender, RoutedEventArgs e)
        {
            SelectEncoding(miUtf8.Tag.ToString());
        }

        /// <summary>
        /// 设置下次编译 Html 时使用 GB2312 编码。
        /// </summary>
        private void miGb2312_Click(object sender, RoutedEventArgs e)
        {
            SelectEncoding(miGb2312.Tag.ToString());
        }

        /// <summary>
        /// 选取（设置）下一次编译 Html 文档时使用什么编码方式。
        /// </summary>
        /// <param name="encoding">目前只支持 GB2312 和 UTF-8 这两种编码。</param>
        private void SelectEncoding(string encoding)
        {
            RefreshEncoding(encoding);

            App.WorkspaceConfigManager.Set("Encoding", encoding);
        }

        /// <summary>
        /// 根据“encoding”的值来刷新主界面上关于编译 Html 时使用的编码方式的两个菜单的选取状态。
        /// </summary>
        /// <param name="encoding"></param>
        private void RefreshEncoding(string encoding)
        {
            if (string.IsNullOrEmpty(encoding))
            {
                encoding = "utf-8";
            }

            if (encoding == "gb2312")
            {
                defaultEncoding = "gb2312";
                miUtf8.IsChecked = false;
                miGb2312.IsChecked = true;
            }
            else//都强制为UTF-8
            {
                if (encoding != "utf-8")
                {
                    encoding = "utf-8";
                }

                miUtf8.IsChecked = true;
                miGb2312.IsChecked = false;
                defaultEncoding = "utf-8";
            }
        }

        /// <summary>
        /// 为当前工作区创建一个 CHM 工程文件（包括对应的目录文件、索引文件）。创建后可以自动调用微软
        /// Html Help Workshop来进行编辑、编译 CHM 帮助文档的操作。
        /// 
        /// 2017年7月20日：
        /// 既然工作区管理器中的条目可以调整层级和位置，那么让用户手动创建 CHM 工程文件就是没有必要的。
        /// 
        /// 所以此方法已无必要存在，而且其中的代码本身就存在问题，仅留存备查。
        /// </summary>
        private void miCreateCHMProject_Click(object sender, RoutedEventArgs e)
        {
            //if (SaveModifiedDocuments() == false) return;

            //try
            //{
            //    var prefixOfFiles = Globals.PathOfWorkspace + Globals.WorkspaceShortName;
            //    var chmProjectFilePath = prefixOfFiles + ".hhp";
            //    var chmContentFilePath = prefixOfFiles + ".hhc";
            //    var chmIndexFilePath = prefixOfFiles + ".hhk";

            //    //var indexHtmlPath = "index.html";

            //    if (File.Exists(chmProjectFilePath) == true)
            //    {
            //        var result = LMessageBox.Show("　　★请慎用此功能！！！\r\n　　它会将你之前创建的所有工程文件全部重置！！！\r\n\r\n" +
            //            "　　所以，除非你从来没有在这个工作区使用过此功能，或者根本不介意之前自己手工修改的成果被全部抹杀——千万别用这个功能！\r\n\r\n" +
            //            "　　你真的要继续吗？", Globals.AppName,
            //             MessageBoxButton.YesNo, MessageBoxImage.Warning);
            //        if (result != MessageBoxResult.Yes) return;
            //    }

            //    //保证创建的工程文件是与当前工作区条目状态一致的。
            //    WorkspaceManager.SaveWorkspaceTreeviewToXml();

            //    //先编译所有Markdown文档。
            //    var result2 = LMessageBox.Show("　　在创建 CHM 工程文件之前，应按 GB2312 编码方式对工作区中所有 Markdown 文件进行编译，要自动进行这步操作吗？", Globals.AppName,
            //         MessageBoxButton.YesNo, MessageBoxImage.Question);
            //    if (result2 == MessageBoxResult.Yes)
            //    {
            //        miGb2312.IsChecked = true;
            //        miGb2312_Click(sender, e);
            //        CompileWholeWorkspace();
            //    }

            //    var gb2312 = Encoding.GetEncoding("gb2312");

            //    //第①步，生成hhp工程文件
            //    var sbProject = new StringBuilder();

            //    #region 工程文件内容

            //    sbProject.Append("[OPTIONS]" + "\r\n");
            //    sbProject.Append("Compatibility= 1.1 or later" + "\r\n");
            //    sbProject.Append("Compiled file = " + Globals.WorkspaceShortName + ".chm\r\n");
            //    sbProject.Append("Contents file = " + Globals.WorkspaceShortName + ".hhc\r\n");
            //    sbProject.Append("Default Font=新宋体,11,1\r\n");

            //    //这里还需要进一步润色。
            //    sbProject.Append("Default topic = _Index.html\r\n");

            //    sbProject.Append("Display compile progress = Yes" + "\r\n");
            //    sbProject.Append("Index file = " + Globals.WorkspaceShortName + ".hhk\r\n");
            //    sbProject.Append("Language = 0x804 中文(简体，中国)" + "\r\n");
            //    sbProject.Append("Title=" + Globals.WorkspaceShortName);

            //    sbProject.Append("\r\n\r\n");

            //    sbProject.Append("[FILES]" + "\r\n");
            //    sbProject.Append("_Index.html\r\n");
            //    sbProject.Append("Images~\\header_icon_dark.png\r\n");
            //    sbProject.Append("Images~\\header_icon_light.png\r\n");
            //    sbProject.Append("Images~\\comment_dark.png\r\n");
            //    sbProject.Append("Images~\\comment_light.png\r\n");
            //    sbProject.Append("Images~\\region_e_dark.png\r\n");
            //    sbProject.Append("Images~\\region_e_light.png\r\n");
            //    sbProject.Append("Images~\\region_i_dark.png\r\n");
            //    sbProject.Append("Images~\\region_i_light.png\r\n");
            //    sbProject.Append("Images~\\region_q_dark.png\r\n");
            //    sbProject.Append("Images~\\region_q_light.png\r\n");
            //    sbProject.Append("Images~\\region_w_dark.png\r\n");
            //    sbProject.Append("Images~\\region_w_light.png\r\n");
            //    sbProject.Append("Images~\\menu_light.png\r\n");
            //    sbProject.Append("Images~\\menu_dark.png\r\n");

            //    sbProject.Append("\r\n");

            //    sbProject.Append("[INFOTYPES]" + "\r\n");

            //    #endregion

            //    File.WriteAllText(chmProjectFilePath, sbProject.ToString(), gb2312);

            //    //第②步，生成hhc目录文件
            //    var sbContent = new StringBuilder();

            //    #region 目录文件内容

            //    //目录文件头，包括首页（_Index.html）
            //    sbContent.Append("<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML//EN\">\r\n");
            //    sbContent.Append("<HTML>\r\n");
            //    sbContent.Append("<HEAD>\r\n");
            //    sbContent.Append("<meta name=\"GENERATOR\" content=\"Microsoft&reg; HTML Help Workshop 4.1\">");
            //    sbContent.Append("<!--Sitemap 1.0-->\r\n");
            //    sbContent.Append("</HEAD><BODY>\r\n");
            //    sbContent.Append("<OBJECT type=\"text /site properties\">\r\n");
            //    sbContent.Append("\t<param name=\"ImageType\" value=\"Folder\">\r\n");
            //    sbContent.Append("</OBJECT>");

            //    //目录文件内容
            //    //如果是子目录，在子目录“< UL >”Tag前加上：
            //    //<LI> <OBJECT type="text/sitemap">
            //    //    < param name = "Name" value = "其它" >
            //    //    </ OBJECT >
            //    //有一层子目录就加一层“< UL >”Tag对

            //    List<string> invalidatedFilePaths;

            //    if (WorkspaceManager.WorkspaceItemsDocument != null && WorkspaceManager.WorkspaceItemsDocument.DocumentElement != null)
            //    {
            //        sbContent.Append("<UL>\n");
            //        sbContent.Append("<LI> <OBJECT type = \"text/sitemap\">\r\n");
            //        sbContent.Append("\t<param name =\"Name\" value=\"目录\">\r\n");
            //        sbContent.Append("\t<param name =\"Local\" value=\"_index.html\">\r\n");
            //        sbContent.Append("\t</OBJECT>\r\n");

            //        invalidatedFilePaths = CreateCHMContentFile(WorkspaceManager.WorkspaceItemsDocument.DocumentElement as XmlNode, "", sbContent);
            //    }
            //    else
            //    {
            //        invalidatedFilePaths = CreateCHMContentFile(new DirectoryInfo(Globals.PathOfWorkspace), "", "", sbContent);
            //    }

            //    //目录文件尾
            //    sbContent.Append("</BODY></HTML>\r\n");

            //    #endregion

            //    File.WriteAllText(chmContentFilePath, sbContent.ToString(), gb2312);

            //    //第③步，生成hhk索引文件
            //    var sbIndex = new StringBuilder();

            //    #region 索引文件内容

            //    //索引文件头，包括首页（_Index.html）
            //    sbIndex.Append("<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML//EN\">\r\n");
            //    sbIndex.Append("<HTML>\r\n");
            //    sbIndex.Append("<HEAD>\r\n");
            //    sbIndex.Append("<meta name=\"GENERATOR\" content=\"Microsoft&reg; HTML Help Workshop 4.1\">\r\n");
            //    sbIndex.Append("<!--Sitemap 1.0-->\r\n");
            //    sbIndex.Append("</HEAD><BODY>\r\n");
            //    sbIndex.Append("<OBJECT type=\"text /site properties\">\r\n");
            //    sbIndex.Append("\t<param name=\"ImageType\" value=\"Folder\">\r\n");
            //    sbIndex.Append("</OBJECT>\r\n");
            //    sbIndex.Append("<UL>\r\n");//索引文件不分层，所以不在内部添加<UL>标签。

            //    //索引文件内容
            //    //如果是子目录，在子目录“< UL >”Tag前加上：
            //    //<LI><OBJECT type="text/sitemap">
            //    //    <param name = "Name" value = "其它">
            //    //    </OBJECT >
            //    //有一层子目录就加一层“<UL>”Tag对

            //    if (WorkspaceManager.WorkspaceItemsDocument != null && WorkspaceManager.WorkspaceItemsDocument.DocumentElement != null)
            //    {
            //        sbIndex.Append("\t<LI> <OBJECT type = \"text/sitemap\">\r\n");
            //        sbIndex.Append("\t\t<param name =\"Name\" value=\"目录\">\r\n");
            //        sbIndex.Append("\t\t<param name =\"Local\" value=\"_index.html\">\r\n");
            //        sbIndex.Append("\t\t</OBJECT>\r\n");

            //        CreateCHMIndexFile(WorkspaceManager.WorkspaceItemsDocument.DocumentElement as XmlNode, sbIndex);
            //    }
            //    else
            //    {
            //        CreateCHMIndexFile(new DirectoryInfo(Globals.PathOfWorkspace), "", sbIndex);
            //    }

            //    //索引文件尾
            //    sbIndex.Append("</UL>\r\n");//索引文件不分层，所以不在内部添加<UL>标签。
            //    sbIndex.Append("</BODY></HTML>\r\n");

            //    #endregion

            //    File.WriteAllText(chmIndexFilePath, sbIndex.ToString(), gb2312);

            //    //第④步，调用hhc.exe编译
            //    //既然已经内置了编辑器，为什么不直接用它来手工修改工程文件再编译呢？

            //    if (invalidatedFilePaths.Count > 0)
            //    {
            //        StringBuilder sb = new StringBuilder();
            //        foreach (string ifp in invalidatedFilePaths)
            //        {
            //            sb.Append("　　" + ifp + "\r\n");
            //        }

            //        LMessageBox.Show("　　创建 CHM 工程文件的过程中发现问题。这可能是由下列原因造成的：\r\n" +
            //            "　　1. 指定的工作区目录中下列文件的物理路径中含有特殊字符，会导致编译的 CHM 文件中的链接失效；\r\n" +
            //            "　　2. 某些 Markdown 文件中可能没有实质性内容（或未能解密），导致无法编译为 Html 文件。\r\n\r\n" +
            //            "　　请检查下列文件是否存在上列问题：\r\n\r\n"
            //            + sb.ToString(), Globals.AppName,
            //             MessageBoxButton.OK, MessageBoxImage.Warning);
            //    }

            //    //编译总是会出错，所以曾经干脆直接集成了Html Help Workshop到安装包里。
            //    //但这又有版权问题，于是最终还是取消了这个做法。
            //    LoadHHWInstalledPath();

            //    if (File.Exists(this.HHWInstalledPath))
            //    {
            //        //System.Diagnostics.Process.Start(this.HHWInstalledPath, $"\"{chmProjectFilePath}\"");
            //        //加引号的办法虽然可以解决路径带空格的问题，但在 Html Help Workshop 中点击“编译”按钮时
            //        //在弹出的路径选取框中获得的路径也会带双引号——这时又会弹出“找不到路径”的错误框。

            //        //下面这个办法也不行，这貌似是 Linux 下的办法？
            //        //if (chmProjectFilePath.Contains(" "))
            //        //{
            //        //    chmProjectFilePath = chmProjectFilePath.Replace(" ", "\\ ");
            //        //}

            //        if (chmProjectFilePath.Contains(" "))
            //        {
            //            LMessageBox.Show("　　您设置的工作区目录完整路径中含有空格，这会导致 Html Help Workshop 在编译时提示无法打开工程文件！\r\n　　请在弹出该消息框后手工选取工程文件位置再编译（办法是去除路径首尾自动添加的双引号）。",
            //                Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning, "", "001_常见问题与错误.html#can_not_find_chm_project");
            //            System.Diagnostics.Process.Start(this.HHWInstalledPath, $"\"{chmProjectFilePath}\"");
            //        }
            //        else
            //        {
            //            System.Diagnostics.Process.Start(this.HHWInstalledPath, chmProjectFilePath);
            //        }
            //    }
            //    else
            //    {
            //        LMessageBox.Show("　　未指定 Microsoft HTML Help Workshop.exe 的磁盘路径，无法调用！\r\n" +
            //            "　　您可能需要到微软公司官方网站下载该工具并安装。",
            //            Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Information);
            //    }

            //    //try
            //    //{
            //    //    //由于物理路径很可能包括空格，所以必须用双引号括起来，不然在命令行中会出错。
            //    //    //var cmd = "\"" + Globals.HtmlHelpCompilerFullPath + "\" \"" + chmProjectFilePath + "\"";

            //    //    System.Diagnostics.Process.Start(Globals.HtmlHelpCompilerFullPath, $"\"{chmProjectFilePath}\"");
            //    //}
            //    //catch (Exception ex)
            //    //{
            //    //    LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace);
            //    //}
            //}
            //catch (Exception ex)
            //{
            //    LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace);
            //}
        }

        /// <summary>
        /// 2017年7月20日：
        /// 今天发现两个新 Bug: “忽略已加密文件”和“忽略被标记为废弃的文件”这两个选项无效。
        /// 继而想到，既然已经可以调整工作区管理器中条目的位置、层级，那么再让用户手动“创建 CHM 工程”就没有什么必要了。
        /// 干脆每次编译前自动重新创建工程文件好了！！！
        /// 所以将旧的方法单独提取出来重新修改成了下面这个方法。
        /// </summary>
        private bool CreateChmProjectFiles()
        {
            if (SaveModifiedDocuments() == false) return false;

            try
            {
                var prefixOfFiles = Globals.PathOfWorkspace + Globals.WorkspaceShortName;
                var chmProjectFilePath = prefixOfFiles + ".hhp";
                var chmContentFilePath = prefixOfFiles + ".hhc";
                var chmIndexFilePath = prefixOfFiles + ".hhk";

                //保证创建的工程文件是与当前工作区条目状态一致的。
                WorkspaceManager.SaveWorkspaceTreeviewToXml();

                //先编译所有Markdown文档。
                var result2 = LMessageBox.Show("　　在创建 CHM 工程文件之前，应按 GB2312 编码方式对工作区中所有 Markdown 文件进行编译，要自动进行这步操作吗？", Globals.AppName,
                     MessageBoxButton.YesNo, MessageBoxImage.Question);
                if (result2 == MessageBoxResult.Yes)
                {
                    miGb2312.IsChecked = true;
                    miGb2312_Click(null, null);
                    CompileWholeWorkspace();
                }

                var gb2312 = Encoding.GetEncoding("gb2312");

                //第①步，生成hhp工程文件
                var sbProject = new StringBuilder();

                #region 工程文件内容

                sbProject.Append("[OPTIONS]" + "\r\n");
                sbProject.Append("Compatibility= 1.1 or later" + "\r\n");
                sbProject.Append("Compiled file = " + Globals.WorkspaceShortName + ".chm\r\n");
                sbProject.Append("Contents file = " + Globals.WorkspaceShortName + ".hhc\r\n");
                sbProject.Append("Default Font=新宋体,11,1\r\n");

                //这里还需要进一步润色。
                sbProject.Append("Default topic = _Index.html\r\n");

                sbProject.Append("Display compile progress = Yes" + "\r\n");
                sbProject.Append("Index file = " + Globals.WorkspaceShortName + ".hhk\r\n");
                sbProject.Append("Language = 0x804 中文(简体，中国)" + "\r\n");
                sbProject.Append("Title=" + Globals.WorkspaceShortName);

                sbProject.Append("\r\n\r\n");

                sbProject.Append("[FILES]" + "\r\n");
                sbProject.Append("_Index.html\r\n");
                sbProject.Append("Images~\\header_icon_dark.png\r\n");
                sbProject.Append("Images~\\header_icon_light.png\r\n");
                sbProject.Append("Images~\\comment_dark.png\r\n");
                sbProject.Append("Images~\\comment_light.png\r\n");
                sbProject.Append("Images~\\region_e_dark.png\r\n");
                sbProject.Append("Images~\\region_e_light.png\r\n");
                sbProject.Append("Images~\\region_i_dark.png\r\n");
                sbProject.Append("Images~\\region_i_light.png\r\n");
                sbProject.Append("Images~\\region_q_dark.png\r\n");
                sbProject.Append("Images~\\region_q_light.png\r\n");
                sbProject.Append("Images~\\region_w_dark.png\r\n");
                sbProject.Append("Images~\\region_w_light.png\r\n");
                sbProject.Append("Images~\\menu_light.png\r\n");
                sbProject.Append("Images~\\menu_dark.png\r\n");

                sbProject.Append("\r\n");

                sbProject.Append("[INFOTYPES]" + "\r\n");

                #endregion

                File.WriteAllText(chmProjectFilePath, sbProject.ToString(), gb2312);

                //第②步，生成hhc目录文件
                var sbContent = new StringBuilder();

                #region 目录文件内容

                //目录文件头，包括首页（_Index.html）
                sbContent.Append("<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML//EN\">\r\n");
                sbContent.Append("<HTML>\r\n");
                sbContent.Append("<HEAD>\r\n");
                sbContent.Append("<meta name=\"GENERATOR\" content=\"Microsoft&reg; HTML Help Workshop 4.1\">");
                sbContent.Append("<!--Sitemap 1.0-->\r\n");
                sbContent.Append("</HEAD><BODY>\r\n");
                sbContent.Append("<OBJECT type=\"text /site properties\">\r\n");
                sbContent.Append("\t<param name=\"ImageType\" value=\"Folder\">\r\n");
                sbContent.Append("</OBJECT>");

                //目录文件内容
                //如果是子目录，在子目录“<UL>”Tag前加上：
                //<LI> <OBJECT type="text/sitemap">
                //    < param name = "Name" value = "其它" >
                //    </ OBJECT >
                //有一层子目录就加一层“<UL>”Tag对

                List<string> invalidatedFilePaths;

                if (WorkspaceManager.WorkspaceItemsDocument != null && WorkspaceManager.WorkspaceItemsDocument.DocumentElement != null)
                {
                    sbContent.Append("<UL>\n");
                    sbContent.Append("<LI> <OBJECT type = \"text/sitemap\">\r\n");
                    sbContent.Append("\t<param name =\"Name\" value=\"目录\">\r\n");
                    sbContent.Append("\t<param name =\"Local\" value=\"_index.html\">\r\n");
                    sbContent.Append("\t</OBJECT>\r\n");

                    //这种是新式的，根据工作区管理器中各条目的布局来创建 CHM 目录文件。
                    List<string> encryptPaths = new List<string>();
                    List<string> abortedPaths = new List<string>();
                    invalidatedFilePaths = CreateCHMContentFile(WorkspaceManager.WorkspaceItemsDocument.DocumentElement as XmlNode, "", sbContent, ref encryptPaths, ref abortedPaths);
                }
                else
                {
                    //这种是旧式的，根据目录层级创建 CHM 目录文件。
                    List<string> encryptPaths = new List<string>();
                    List<string> abortedPaths = new List<string>();
                    invalidatedFilePaths = CreateCHMContentFile(new DirectoryInfo(Globals.PathOfWorkspace), "", "", sbContent, ref encryptPaths, ref abortedPaths);
                }

                //目录文件尾
                sbContent.Append("</BODY></HTML>\r\n");

                #endregion

                File.WriteAllText(chmContentFilePath, sbContent.ToString(), gb2312);

                //第③步，生成hhk索引文件
                var sbIndex = new StringBuilder();

                #region 索引文件内容

                //索引文件头，包括首页（_Index.html）
                sbIndex.Append("<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML//EN\">\r\n");
                sbIndex.Append("<HTML>\r\n");
                sbIndex.Append("<HEAD>\r\n");
                sbIndex.Append("<meta name=\"GENERATOR\" content=\"Microsoft&reg; HTML Help Workshop 4.1\">\r\n");
                sbIndex.Append("<!--Sitemap 1.0-->\r\n");
                sbIndex.Append("</HEAD><BODY>\r\n");
                sbIndex.Append("<OBJECT type=\"text /site properties\">\r\n");
                sbIndex.Append("\t<param name=\"ImageType\" value=\"Folder\">\r\n");
                sbIndex.Append("</OBJECT>\r\n");
                sbIndex.Append("<UL>\r\n");//索引文件不分层，所以不在内部添加<UL>标签。

                //索引文件内容
                //如果是子目录，在子目录“< UL >”Tag前加上：
                //<LI><OBJECT type="text/sitemap">
                //    <param name = "Name" value = "其它">
                //    </OBJECT >
                //有一层子目录就加一层“<UL>”Tag对

                if (WorkspaceManager.WorkspaceItemsDocument != null && WorkspaceManager.WorkspaceItemsDocument.DocumentElement != null)
                {
                    sbIndex.Append("\t<LI> <OBJECT type = \"text/sitemap\">\r\n");
                    sbIndex.Append("\t\t<param name =\"Name\" value=\"目录\">\r\n");
                    sbIndex.Append("\t\t<param name =\"Local\" value=\"_index.html\">\r\n");
                    sbIndex.Append("\t\t</OBJECT>\r\n");

                    //这种是新式的，按照工作区管理器中条目的布局来创建索引文件。
                    List<string> encryptdPaths = new List<string>();
                    List<string> abortedPaths = new List<string>();
                    CreateCHMIndexFile(WorkspaceManager.WorkspaceItemsDocument.DocumentElement as XmlNode, sbIndex, ref encryptdPaths, ref abortedPaths);
                }
                else
                {
                    //这种是旧式的，直接按目录来生成索引文件。
                    List<string> encryptdPaths = new List<string>();
                    List<string> abortedPaths = new List<string>();
                    CreateCHMIndexFile(new DirectoryInfo(Globals.PathOfWorkspace), "", sbIndex, ref encryptdPaths, ref abortedPaths);
                }

                //索引文件尾
                sbIndex.Append("</UL>\r\n");//索引文件不分层，所以不在内部添加<UL>标签。
                sbIndex.Append("</BODY></HTML>\r\n");

                #endregion

                File.WriteAllText(chmIndexFilePath, sbIndex.ToString(), gb2312);

                //第④步，查找 HHW 的安装位置，准备调用hhc.exe编译
                if (invalidatedFilePaths.Count > 0)
                {
                    StringBuilder sb = new StringBuilder();
                    foreach (string ifp in invalidatedFilePaths)
                    {
                        sb.Append("　　" + ifp + "\r\n");
                    }

                    LMessageBox.Show("　　创建 CHM 工程文件的过程中发现问题。这可能是由下列原因造成的：\r\n" +
                        "　　1. 指定的工作区目录中下列文件的物理路径中含有特殊字符，会导致编译的 CHM 文件中的链接失效；\r\n" +
                        "　　2. 某些 Markdown 文件中可能没有实质性内容（或未能解密），导致无法编译为 Html 文件。\r\n\r\n" +
                        "　　请检查下列文件是否存在上列问题：\r\n\r\n"
                        + sb.ToString(), Globals.AppName,
                         MessageBoxButton.OK, MessageBoxImage.Warning);
                    return false;
                }

                //寻找 Html Help Workshop 的安装位置。
                LoadHHWInstalledPath();

                if (File.Exists(this.HHWInstalledPath) == false)
                {
                    LMessageBox.Show("　　未指定 Microsoft HTML Help Workshop.exe 的磁盘路径，无法调用！\r\n" +
                        "　　您可能需要到微软公司官方网站下载该工具并安装。",
                        Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Information);
                    return false;
                }

                return true;
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace);
                return false;
            }
        }

        /// <summary>
        /// 载入 Html Help Workshop 的安装位置信息。
        /// </summary>
        private void LoadHHWInstalledPath()
        {
            if (File.Exists(this.HHWInstalledPath) == false)
            {
                var result = LMessageBox.Show("　　请指定 Microsoft Html Help Workshop 所在的磁盘路径：\r\n" +
                    "　　通常它的文件名应是 Microsoft HTML Help Workshop.exe 。\r\n\r\n　　要现在指定吗？", Globals.AppName,
                     MessageBoxButton.YesNo, MessageBoxImage.Information);

                if (result != MessageBoxResult.Yes) return;
                SetHtmlHelpWorkshopFullName();
            }
        }

        /// <summary>
        /// 指定安装的 Html Help Workshop 的路径。
        /// 程序会自动通过注册表寻找安装的 Html Help Workshop。
        /// 但有时我们下载的不是微软公司的官方版本，很可能是网友修改过的汉化版——此时注册表中未必有这个安装位置信息。
        /// </summary>
        private void SetHtmlHelpWorkshopFullName()
        {
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.Multiselect = false;
            ofd.Title = Globals.AppName + "请指定 Microsoft Html Help Workshop 路径：";
            ofd.Filter = "可执行文件(*.exe)|*.exe";

            if (ofd.ShowDialog() == true)
            {
                App.ConfigManager.Set("HHWInstalledPath", ofd.FileName);
                this.hhwInstalledPath = ofd.FileName;

                var fileInfo = new FileInfo(this.hhwInstalledPath);
                var hhcInstalledPath = fileInfo.DirectoryName + "\\hhc.exe";
                if (File.Exists(hhcInstalledPath))
                {
                    Globals.hhcInstalledPath = hhcInstalledPath;
                    App.ConfigManager.Set("HHCInstalledPath", hhcInstalledPath);
                }
            }
        }

        /// <summary>
        /// 载入 Html Help Workshop 的编译器的安装位置信息。
        /// </summary>
        private void LoadHHCInstalledPath()
        {
            if (File.Exists(this.HHCInstalledPath) == false)
            {
                var result = LMessageBox.Show("　　请指定 Microsoft Html Help Workshop 的编译器文件所在的磁盘路径：\r\n" +
                    "　　通常它的文件名应是 hhc.exe 。\r\n\r\n　　要现在指定吗？", Globals.AppName,
                     MessageBoxButton.YesNo, MessageBoxImage.Information);

                if (result != MessageBoxResult.Yes) return;
                SetHtmlHelpWorkshopCompilerFullName();
            }
        }

        /// <summary>
        /// 指定安装的 Html Help Workshop 的路径。
        /// 程序会自动通过注册表寻找安装的 Html Help Workshop。
        /// 但有时我们下载的不是微软公司的官方版本，很可能是网友修改过的汉化版——此时注册表中未必有这个安装位置信息。
        /// </summary>
        private void SetHtmlHelpWorkshopCompilerFullName()
        {
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.Multiselect = false;
            ofd.Title = Globals.AppName + "请指定 Microsoft Html Help Workshop 编译器（hhc.exe）的路径：";
            ofd.Filter = "可执行文件(*.exe)|*.exe";

            if (ofd.ShowDialog() == true)
            {
                App.ConfigManager.Set("HHCInstalledPath", ofd.FileName);
                this.hhcInstalledPath = ofd.FileName;

                if (File.Exists(Globals.hhwInstalledPath) == false)
                {
                    var fileInfo = new FileInfo(this.hhwInstalledPath);
                    var hhwInstalledPath = fileInfo.DirectoryName + "\\Microsoft Html Help Workshop.exe";
                    if (File.Exists(hhwInstalledPath))
                    {
                        Globals.hhwInstalledPath = hhwInstalledPath;
                        App.ConfigManager.Set("HHWInstalledPath", hhwInstalledPath);
                    }
                }
            }
        }

        /// <summary>
        /// 与InputBox中的同名方法不同：这里要考虑路径分割符和盘符。
        /// </summary>
        /// <param name="text">要检查的源文本。</param>
        /// <param name="highLevel">是否只允许字母、数字、下划线字符。</param>
        /// <returns>为真表示可以用作文件名。</returns>
        public static bool ValidateFilePath(string text, bool highLevel)
        {
            if (string.IsNullOrEmpty(text))
                return false;

            if (highLevel)
            {
                return Regex.Match(text, @"([a-zA-Z]:\\){0,1}([a-zA-Z0-9_\.]\\{1,})").Success;
            }

            if (text.Contains("/") ||
               //text.Contains("\\") ||     //路径中必须带分隔符，故这行不能加。
               //text.Contains(":") ||      //完整的路径中带盘符，故冒号不能加。
               text.Contains("*") ||
               text.Contains("\"") ||
               text.Contains("<") ||
               text.Contains(">") ||
               text.Contains("|") ||
               text.Contains("[") || //方括号会导致图像链接文本切分时识别出错。
               text.Contains("]"))
            {
                return false;
            }

            //带冒号的路径要考虑盘符的问题。
            if (text.Contains(":") || text.Contains("\\"))
            {
                if (text.Length <= 3)
                    return false;

                var subText1 = text.Substring(0, 3);
                if (subText1.EndsWith(":\\") == false)
                    return false;
                var firstChar = subText1[0];
                if (((firstChar >= 'a' && firstChar <= 'z') || (firstChar >= 'A' && firstChar <= 'Z')) == false)
                    return false;
                //以上可以确定不是盘符

                var subText2 = text.Substring(3);
                if (subText2.Contains(":"))
                    return false;//除盘符外，其它位置不应存在冒号

                if (subText2.StartsWith("\\") || subText2.EndsWith("\\"))
                    return false;
                //除开盘符后，路径的剩余部分不应以反斜杠开头；
                //即使不是以反斜杠开头，也仍然可能是个目录（目录以反斜杠结尾）
            }


            for (int i = 0; i < text.Length; i++)
            {
                char c = text[i];
                if (i == 2 && c == ':') continue;

                //全角标点均可用，半角标点不允许前列特殊字符。另，
                //半角波形符用于资源文件夹，不可用。
                //下划线用于目录元文件，不可用。
                //反引号符备用，不可用。

                //与InputBox不同，这里要允许冒号（盘符要用冒号）。
                if (@"""#%&()+,;<=>?@|".IndexOf(c) >= 0) return false;

                //与InputBox下的同名方法不同：这里要允许存在路径分隔符和冒号（盘符要用）
                if ("/\\!@$^-_{}',.～·＠＃￥％…＆＊（）【】｛｝｜；‘’“”：，。《〈〉》？".IndexOf(c) >= 0) continue;

                //全角字符就算是特殊符号也是可以的
                //全角小写字母
                if (c == '、') continue;
                if (c >= 'ａ' && c <= 'ｚ') continue;
                //全角大写字母
                if (c >= 'Ａ' && c <= 'Ｚ') continue;
                //！＂＃＄％＆＇（）＊＋，－．／
                if (c >= '！' && c <= '／') continue;
                //全角数字
                if (c >= '０' && c <= '９') continue;
                //：；＜＝＞？＠
                if (c >= '：' && c <= '＠') continue;
                //［＼］＾＿｀
                if (c >= '［' && c <= '｀') continue;
                //｛｜｝～
                if (c >= '｛' && c <= '～') continue;
                //￠￡￢￣￤￥
                if (c >= '￠' && c <= '￥') continue;
                //全角字符就算是特殊符号也是可以的

                //全角问号？就是>0x9fbb的
                if (c > 0x9fbb)
                {
                    if (c == '§') continue;
                    if (c == '☆') continue;
                    if (c == '★') continue;
                    if (c == '○') continue;
                    if (c == '●') continue;
                    if (c == '◎') continue;
                    if (c == '◇') continue;
                    if (c == '◆') continue;
                    if (c == '□') continue;
                    if (c == '■') continue;
                    if (c == '△') continue;
                    if (c == '▲') continue;
                    if (c == '※') continue;
                    if (c == '〓') continue;
                    if (c == '＃') continue;
                    if (c == '＆') continue;
                    if (c == '＠') continue;
                    if (c == '＿') continue;
                    if (c == '￣') continue;

                    return false;
                }

                if (c < 0x4e00)
                {
                    if (c == '\\') continue;
                    if (c == ':') continue;
                    if (c >= '0' && c <= '9') continue;
                    if (c == '_') continue;
                    if (c == '+') continue;
                    if (c == '-') continue;
                    if (c == '.') continue;//后缀名需要
                    if (c == '=') continue;
                    if (c == ' ') continue;
                    if (c == '(') continue;
                    if (c == ')') continue;
                    if (c == '）') continue;
                    if (c == '（') continue;
                    if (c >= '⑴' && c <= '⒇') continue;
                    if (c >= '①' && c <= '⑩') continue;
                    if (c >= '⒈' && c <= '⒛') continue;
                    if (c >= 'A' && c <= 'Z') continue;
                    if (c >= 'a' && c <= 'z') continue;

                    return false;
                }
            }

            return true;
        }

        /// <summary>
        /// 生成CHM工程所需要的目录文件。
        /// </summary>
        /// <param name="entryNode">Markdown 文件条目或者（非资源的）目录条目。</param>
        /// <param name="prefixTab">第一层应传入空字符串，不需要\t。</param>
        /// <param name="prefixPath">表示目录层级关系的前缀文本。</param>
        /// <param name="sBuilder">文本缓存。</param>
        /// <returns>返回带特殊字符的或无效文件的物理路径列表。</returns>
        private List<string> CreateCHMContentFile(XmlNode entryNode, string prefixTab, StringBuilder sBuilder, ref List<string> encryptedPaths, ref List<string> abortedPaths)
        {
            if (entryNode == null || sBuilder == null)
                return new List<string>();//返回空列表，避免返回null。

            var invalidatedFilePaths = new List<string>();

            var itemTypeAttr = entryNode.GetAttribute("ItemType");
            WorkspaceTreeViewItem.Type itemType;
            if (Enum.TryParse<WorkspaceTreeViewItem.Type>(itemTypeAttr.Value, out itemType) == false)
            {
                itemType = WorkspaceTreeViewItem.Type.Folder;
            }

            switch (itemType)
            {
                case WorkspaceTreeViewItem.Type.File:
                    {
                        var title = "";
                        var attrTitle = entryNode.GetAttribute("Title");
                        if (attrTitle != null && string.IsNullOrWhiteSpace(attrTitle.Value) == false)
                        {
                            title = attrTitle.Value;
                        }

                        var attrPath = entryNode.GetAttribute("Path");
                        if (attrPath != null)
                        {
                            var path = attrPath.Value;

                            var filePath = Globals.PathOfWorkspace + attrPath.Value;

                            #region 判断是否需要跳过此文件
                            if (Globals.MainWindow.IgnoreEncryptedFiles)
                            {
                                //编译工作区时忽略已加密文件
                                string password, passwordTip;
                                var isEncrypted = CustomMarkdownSupport.IsFileEncrypted(filePath, out password, out passwordTip);
                                if (isEncrypted)
                                {
                                    encryptedPaths.Add(filePath);
                                    break;//忽略被加密的文档。
                                }
                            }

                            if (Globals.MainWindow.IgnoreAbortedFiles)
                            {
                                //编译工作区时忽略被标记为“废弃”的文件。
                                if (CustomMarkdownSupport.IsFileAborted(filePath))
                                {
                                    abortedPaths.Add(filePath);
                                    break; ;
                                }
                            }
                            #endregion

                            if (path.ToLower().StartsWith(Globals.PathOfWorkspace.ToLower()))
                            {
                                path = path.Substring(Globals.PathOfWorkspace.Length);
                            }

                            if (path.EndsWith(".md"))
                            {
                                path = path.Substring(0, path.Length - 3) + ".html";
                            }

                            if (File.Exists(Globals.PathOfWorkspace + path) == false)
                            {
                                invalidatedFilePaths.Add(path);
                            }

                            path = CustomMarkdownSupport.UrlEncode(path);

                            sBuilder.Append($"{prefixTab}<LI> <OBJECT type=\"text/sitemap\">\r\n");
                            sBuilder.Append($"{prefixTab}\t<param name=\"Name\" value=\"{title}\">\r\n");
                            sBuilder.Append($"{prefixTab}\t<param name=\"Local\" value=\"{path}\">\r\n");
                            sBuilder.Append($"</OBJECT>\r\n");

                        }
                        break;
                    }
                case WorkspaceTreeViewItem.Type.Folder:
                    {

                        var title = "";
                        var attrTitle = entryNode.GetAttribute("Title");
                        if (attrTitle != null && string.IsNullOrWhiteSpace(attrTitle.Value) == false)
                        {
                            title = attrTitle.Value;
                        }

                        var attrPath = entryNode.GetAttribute("Path");
                        var filePath = attrPath.Value;
                        if (filePath.ToLower().StartsWith(Globals.PathOfWorkspace.ToLower()) == false)
                        {
                            filePath = Globals.PathOfWorkspace + filePath;
                        }

                        filePath = GetMetaFilePathOfDirectory(filePath);

                        //要判断元文件是否被加密、被标记为“废弃”
                        //如果目录元文件被标记为“废弃”，则整个目录被跳过！！！

                        #region 判断是否需要跳过此文件
                        if (Globals.MainWindow.IgnoreEncryptedFiles)
                        {
                            //编译工作区时忽略已加密文件
                            string password, passwordTip;
                            var isEncrypted = CustomMarkdownSupport.IsFileEncrypted(filePath, out password, out passwordTip);
                            if (isEncrypted)
                            {
                                encryptedPaths.Add(filePath);
                                break;//忽略被加密的文档。
                            }
                        }

                        if (Globals.MainWindow.IgnoreAbortedFiles)
                        {
                            //编译工作区时忽略被标记为“废弃”的文件。
                            if (CustomMarkdownSupport.IsFileAborted(filePath))
                            {
                                abortedPaths.Add(filePath);
                                break; ;
                            }
                        }
                        #endregion

                        var path = filePath;
                        if (path.ToLower().StartsWith(Globals.PathOfWorkspace.ToLower()))
                        {
                            path = path.Substring(Globals.PathOfWorkspace.Length);
                        }

                        if (path.EndsWith(".md"))
                        {
                            path = path.Substring(0, path.Length - 3) + ".html";
                        }

                        if (File.Exists(Globals.PathOfWorkspace + path) == false)
                        {
                            invalidatedFilePaths.Add(path);
                        }

                        path = CustomMarkdownSupport.UrlEncode(path);

                        sBuilder.Append($"{prefixTab}<LI> <OBJECT type=\"text/sitemap\">\r\n");
                        sBuilder.Append($"{prefixTab}<param name=\"Name\" value=\"{title}\">\r\n");
                        sBuilder.Append($"{prefixTab}<param name=\"Local\" value=\"{path}\">\r\n");
                        sBuilder.Append($"</OBJECT>\r\n");

                        if (entryNode.ChildNodes.Count > 0)
                        {
                            prefixTab += "\t";
                            sBuilder.Append($"{prefixTab}<UL>\r\n");

                            foreach (XmlNode childNode in entryNode.ChildNodes)
                            {
                                var subInvalidatedFilePaths = CreateCHMContentFile(childNode, prefixTab, sBuilder, ref encryptedPaths, ref abortedPaths);
                                invalidatedFilePaths.AddRange(subInvalidatedFilePaths);
                            }

                            sBuilder.Append($"{prefixTab}</UL>\r\n");
                        }

                        break;
                    }
            }
            return invalidatedFilePaths;
        }

        /// <summary>
        /// 生成CHM工程所需要的目录文件。
        /// </summary>
        /// <param name="directoryInfo"></param>
        /// <param name="prefixTab">第一层应传入空字符串，不需要\t。</param>
        /// <param name="prefixPath">表示目录层级关系的前缀文本。</param>
        /// <param name="sBuilder">文本缓存。</param>
        /// <returns>返回带特殊字符的物理路径列表。</returns>
        private List<string> CreateCHMContentFile(DirectoryInfo directoryInfo, string prefixTab, string prefixPath, StringBuilder sBuilder, ref List<string> encryptedPaths, ref List<string> abortedPaths)
        {
            if (directoryInfo == null || directoryInfo.Exists == false || prefixPath == null || sBuilder == null)
                return new List<string>();//返回空列表，避免返回null。

            if (prefixPath != "" && prefixPath.EndsWith("/") == false) prefixPath += "/";

            sBuilder.Append($"{prefixTab}<UL>\r\n");

            List<string> invalidatedFilePaths = new List<string>();

            List<ChmContentEntry> chmContentEntries = new List<ChmContentEntry>();

            var files = directoryInfo.GetFiles();
            if (files.Length > 0)
            {
                foreach (var fileInfo in files)
                {
                    if (fileInfo.FullName.ToLower().EndsWith(".html") == false) continue;
                    if (fileInfo.FullName.ToLower().EndsWith("~.html")) continue;

                    if (fileInfo.Name.ToLower() == ("_" + directoryInfo + ".html").ToLower()) continue;//这个文件是指向CHM目录节点的文件。不在这里添加目录引用。

                    if (ValidateFilePath(fileInfo.FullName, true) == false)
                    {
                        invalidatedFilePaths.Add(fileInfo.FullName);
                    }

                    #region 判断是否需要跳过此文件
                    if (Globals.MainWindow.IgnoreEncryptedFiles)
                    {
                        //编译工作区时忽略已加密文件
                        string password, passwordTip;
                        var isEncrypted = CustomMarkdownSupport.IsFileEncrypted(fileInfo.FullName, out password, out passwordTip);
                        if (isEncrypted)
                        {
                            encryptedPaths.Add(fileInfo.FullName);
                            continue;//忽略被加密的文档。
                        }
                    }

                    if (Globals.MainWindow.IgnoreAbortedFiles)
                    {
                        //编译工作区时忽略被标记为“废弃”的文件。
                        if (CustomMarkdownSupport.IsFileAborted(fileInfo.FullName))
                        {
                            abortedPaths.Add(fileInfo.FullName);
                            continue;
                        }
                    }
                    #endregion

                    string contentEntryName = GetTitleOfMdFile(fileInfo.FullName);

                    if (string.IsNullOrEmpty(contentEntryName))
                    {
                        var shortName = fileInfo.Name;
                        if (shortName.StartsWith("_") && shortName.Length > 1)
                        {
                            shortName = shortName.Substring(1);
                        }

                        if (shortName.ToLower() == "index.html" || shortName.ToLower() == "_index.html")
                        {
                            shortName = "目录";
                        }
                        else
                        {
                            if (shortName.ToLower().EndsWith(".html") && shortName.Length > 5)
                            {
                                shortName = shortName.Substring(0, shortName.Length - 5);
                            }
                        }

                        contentEntryName = shortName;
                    }

                    chmContentEntries.Add(new ChmContentEntry()
                    {
                        FileSystemInfo = fileInfo,
                        ShortName = fileInfo.Name,
                        Title = contentEntryName,
                        IsFile = true,
                    });
                }
            }

            var subDirectories = directoryInfo.GetDirectories();
            if (subDirectories.Length > 0)
            {
                //prefixTab += "\t";
                foreach (var subDirectory in subDirectories)
                {
                    if (subDirectory.FullName.EndsWith("~")) continue;

                    var subFiles = subDirectory.GetFiles();
                    var htmlCount = 0;
                    string directoryMetaFile = null;
                    foreach (var subfile in subFiles)
                    {
                        if (subfile.FullName.ToLower().EndsWith(".html"))
                        {
                            htmlCount++;
                            if (subfile.Name.ToLower() == ("_" + subDirectory.Name + ".html").ToLower())
                            {
                                directoryMetaFile = subfile.Name;
                            }
                        }
                    }

                    //要判断元文件是否被加密、被标记为“废弃”
                    //如果目录元文件被标记为“废弃”，则整个目录被跳过！！！
                    var subDirectoryMetaFilePath = subDirectory.FullName + "\\" + directoryMetaFile;

                    #region 判断是否需要跳过此文件
                    if (Globals.MainWindow.IgnoreEncryptedFiles)
                    {
                        //编译工作区时忽略已加密文件
                        string password, passwordTip;
                        var isEncrypted = CustomMarkdownSupport.IsFileEncrypted(subDirectoryMetaFilePath, out password, out passwordTip);
                        if (isEncrypted)
                        {
                            encryptedPaths.Add(subDirectoryMetaFilePath);
                            continue;//忽略被加密的文档。
                        }
                    }

                    if (Globals.MainWindow.IgnoreAbortedFiles)
                    {
                        //编译工作区时忽略被标记为“废弃”的文件。
                        if (CustomMarkdownSupport.IsFileAborted(subDirectoryMetaFilePath))
                        {
                            abortedPaths.Add(subDirectoryMetaFilePath);
                            continue;
                        }
                    }
                    #endregion

                    var shortTitle = GetTitleOfMdFile(subDirectoryMetaFilePath);
                    if (string.IsNullOrEmpty(shortTitle))
                    {
                        shortTitle = subDirectory.Name;
                    }

                    chmContentEntries.Add(new ChmContentEntry()
                    {
                        IsDirectory = true,
                        FileSystemInfo = subDirectory,
                        ShortName = subDirectory.Name,
                        Title = shortTitle,
                        DirectoryMetaFile = directoryMetaFile,
                    });

                }
            }

            List<ChmContentEntry> tempList2 = new List<ChmContentEntry>();
            List<ChmContentEntry> tempList3 = new List<ChmContentEntry>();

            foreach (var c in chmContentEntries)
            {
                if (c.ShortName.StartsWith("_"))
                {
                    tempList2.Add(c);
                }
                else
                {
                    tempList3.Add(c);
                }
            }

            tempList2.Sort(new ChmContentEntryCompare());
            tempList3.Sort(new ChmContentEntryCompare());

            foreach (var ce in tempList2)
            {
                if (ce.IsFile)
                {
                    sBuilder.Append($"{prefixTab}<LI> <OBJECT type = \"text/sitemap\">\r\n");
                    sBuilder.Append($"{prefixTab}\t<param name =\"Name\" value=\"{ce.Title}\">\r\n");
                    sBuilder.Append($"{prefixTab}\t<param name =\"Local\" value=\"{CustomMarkdownSupport.UrlEncode(prefixPath)}{ce.UrlName}\">\r\n");
                    sBuilder.Append($"{prefixTab}\t</OBJECT>\r\n");
                }
                else if (ce.IsDirectory)
                {
                    prefixTab += "\t";
                    //先加目录，即使是空目录也应该添加一条目录条目
                    sBuilder.Append($"{prefixTab}<LI> <OBJECT type = \"text/sitemap\">\r\n");
                    sBuilder.Append($"{prefixTab}\t<param name =\"Name\" value=\"{ce.Title}\">\r\n");

                    //目录节点在CHM中指向的文件应是该目录下名为“_目录短名.html”的文件。
                    if (string.IsNullOrWhiteSpace(ce.DirectoryMetaFile) == false)
                    {
                        sBuilder.Append($"{prefixTab}\t<param name =\"Local\" value=\"{prefixPath}{ce.UrlName}\\{ce.DirectoryMetaFile}\">\r\n");
                    }

                    sBuilder.Append($"{prefixTab}\t</OBJECT>\r\n");

                    var subPrefixPath = prefixPath + ce.UrlName;

                    //因为是递归的，所以不能直接用下面这行：
                    //hasInvalidatedFilePath = CreateContentDirectory(subDirectiry, prefixTab, subPrefixPath, sb);

                    var subInvalidatedFilePaths = CreateCHMContentFile(ce.FileSystemInfo as DirectoryInfo, prefixTab, subPrefixPath, sBuilder, ref encryptedPaths, ref abortedPaths);
                    if (subInvalidatedFilePaths.Count > 0)
                    {
                        invalidatedFilePaths.AddRange(subInvalidatedFilePaths);
                    }
                    prefixTab = prefixTab.Substring(0, prefixTab.Length - 1);
                }
            }

            foreach (var ce in tempList3)
            {
                if (ce.IsFile)
                {
                    sBuilder.Append($"{prefixTab}<LI> <OBJECT type = \"text/sitemap\">\r\n");
                    sBuilder.Append($"{prefixTab}\t<param name =\"Name\" value=\"{ce.Title}\">\r\n");
                    sBuilder.Append($"{prefixTab}\t<param name =\"Local\" value=\"{prefixPath}{ce.UrlName}\">\r\n");
                    sBuilder.Append($"{prefixTab}\t</OBJECT>\r\n");
                }
                else if (ce.IsDirectory)
                {
                    prefixTab += "\t";
                    //先加目录，即使是空目录也应该添加一条目录条目
                    sBuilder.Append($"{prefixTab}<LI> <OBJECT type = \"text/sitemap\">\r\n");
                    sBuilder.Append($"{prefixTab}\t<param name =\"Name\" value=\"{FormatDocumentTitle(ce.Title)}\">\r\n");

                    //目录节点在CHM中指向的文件应是该目录下名为“_目录短名.html”的文件。
                    if (string.IsNullOrWhiteSpace(ce.DirectoryMetaFile) == false)
                    {
                        sBuilder.Append($"{prefixTab}\t<param name =\"Local\" value=\"{prefixPath}{ce.UrlName}\\{ce.DirectoryMetaFile}\">\r\n");
                    }

                    sBuilder.Append($"{prefixTab}\t</OBJECT>\r\n");

                    var subPrefixPath = prefixPath + ce.UrlName;

                    //因为是递归的，所以不能直接用下面这行：
                    //invalidatedFilePaths = CreateContentDirectory(subDirectiry, prefixTab, subPrefixPath, sb);

                    var subInvalidatedFilePaths = CreateCHMContentFile(ce.FileSystemInfo as DirectoryInfo, prefixTab, subPrefixPath, sBuilder, ref encryptedPaths, ref abortedPaths);
                    if (invalidatedFilePaths.Count > 0)
                    {
                        invalidatedFilePaths.AddRange(subInvalidatedFilePaths);
                    }
                    prefixTab = prefixTab.Substring(0, prefixTab.Length - 1);
                }
            }

            sBuilder.Append($"{prefixTab}</UL>\r\n");
            return invalidatedFilePaths;
        }

        /// <summary>
        /// 生成CHM工程所需要的索引文件。与目录文件不同，索引文件不分层级。
        /// </summary>
        /// <param name="directoryNode">基准目录，通常应传入工作区目录。</param>
        /// <param name="prefixPath">表示目录层级关系的前缀。</param>
        /// <param name="sBuilder">文本缓存。</param>
        /// <returns>返回含有特殊字符的文件路径的列表。</returns>
        private List<string> CreateCHMIndexFile(XmlNode entryNode, StringBuilder sBuilder, ref List<string> encryptedPaths, ref List<string> abortedPaths)
        {
            if (entryNode == null || sBuilder == null)
                return new List<string>();//返回空列表，避免返回null。

            var invalidatedFilePaths = new List<string>();

            var itemTypeAttr = entryNode.GetAttribute("ItemType");
            WorkspaceTreeViewItem.Type itemType;
            if (Enum.TryParse<WorkspaceTreeViewItem.Type>(itemTypeAttr.Value, out itemType) == false)
            {
                itemType = WorkspaceTreeViewItem.Type.Folder;
            }

            switch (itemType)
            {
                case WorkspaceTreeViewItem.Type.File:
                    {
                        var title = "";
                        var attrTitle = entryNode.GetAttribute("Title");
                        if (attrTitle != null && string.IsNullOrWhiteSpace(attrTitle.Value) == false)
                        {
                            title = attrTitle.Value;
                        }

                        var attrPath = entryNode.GetAttribute("Path");
                        if (attrPath != null)
                        {
                            var path = attrPath.Value;
                            var filePath = Globals.PathOfWorkspace + attrPath.Value;

                            #region 判断是否需要跳过此文件
                            if (Globals.MainWindow.IgnoreEncryptedFiles)
                            {
                                //编译工作区时忽略已加密文件
                                string password, passwordTip;
                                var isEncrypted = CustomMarkdownSupport.IsFileEncrypted(filePath, out password, out passwordTip);
                                if (isEncrypted)
                                {
                                    encryptedPaths.Add(filePath);
                                    break;//忽略被加密的文档。
                                }
                            }

                            if (Globals.MainWindow.IgnoreAbortedFiles)
                            {
                                //编译工作区时忽略被标记为“废弃”的文件。
                                if (CustomMarkdownSupport.IsFileAborted(filePath))
                                {
                                    abortedPaths.Add(filePath);
                                    break;
                                }
                            }
                            #endregion

                            if (path.ToLower().StartsWith(Globals.PathOfWorkspace.ToLower()))
                            {
                                path = path.Substring(Globals.PathOfWorkspace.Length);
                            }

                            if (path.EndsWith(".md"))
                            {
                                path = path.Substring(0, path.Length - 3) + ".html";
                            }

                            if (File.Exists(Globals.PathOfWorkspace + path) == false)
                            {
                                invalidatedFilePaths.Add(path);
                            }

                            path = CustomMarkdownSupport.UrlEncode(path);

                            sBuilder.Append($"\t<LI> <OBJECT type=\"text/sitemap\">\r\n");
                            sBuilder.Append($"\t\t<param name=\"Name\" value=\"{title}\">\r\n");
                            sBuilder.Append($"\t\t<param name=\"Local\" value=\"{path}\">\r\n");
                            sBuilder.Append("\t\t</OBJECT>\r\n");

                        }
                        break;
                    }
                case WorkspaceTreeViewItem.Type.Folder:
                    {
                        var title = "";
                        var attrTitle = entryNode.GetAttribute("Title");
                        if (attrTitle != null && string.IsNullOrWhiteSpace(attrTitle.Value) == false)
                        {
                            title = attrTitle.Value;
                        }

                        var attrPath = entryNode.GetAttribute("Path");
                        var filePath = attrPath.Value;
                        if (filePath.ToLower().StartsWith(Globals.PathOfWorkspace.ToLower()) == false)
                        {
                            filePath = Globals.PathOfWorkspace + filePath;
                        }

                        filePath = GetMetaFilePathOfDirectory(filePath);

                        #region 判断是否需要跳过此文件
                        if (Globals.MainWindow.IgnoreEncryptedFiles)
                        {
                            //编译工作区时忽略已加密文件
                            string password, passwordTip;
                            var isEncrypted = CustomMarkdownSupport.IsFileEncrypted(filePath, out password, out passwordTip);
                            if (isEncrypted)
                            {
                                encryptedPaths.Add(filePath);
                                break;//忽略被加密的元文档及整个子目录。
                            }
                        }

                        if (Globals.MainWindow.IgnoreAbortedFiles)
                        {
                            //编译工作区时忽略被标记为“废弃”的元文档及整个子目录。
                            if (CustomMarkdownSupport.IsFileAborted(filePath))
                            {
                                abortedPaths.Add(filePath);
                                break;
                            }
                        }
                        #endregion

                        var path = filePath;
                        if (path.ToLower().StartsWith(Globals.PathOfWorkspace.ToLower()))
                        {
                            path = path.Substring(Globals.PathOfWorkspace.Length);
                        }

                        if (path.EndsWith(".md"))
                        {
                            path = path.Substring(0, path.Length - 3) + ".html";
                        }

                        if (File.Exists(Globals.PathOfWorkspace + path) == false)
                        {
                            invalidatedFilePaths.Add(path);
                        }

                        path = CustomMarkdownSupport.UrlEncode(path);

                        sBuilder.Append($"\t<LI> <OBJECT type=\"text/sitemap\">\r\n");
                        sBuilder.Append($"\t\t<param name=\"Name\" value=\"{title}\">\r\n");
                        sBuilder.Append($"\t\t<param name=\"Local\" value=\"{path}\">\r\n");
                        sBuilder.Append($"\t\t</OBJECT>\r\n");

                        if (entryNode.ChildNodes.Count > 0)
                        {
                            foreach (XmlNode childNode in entryNode.ChildNodes)
                            {
                                var subInvalidatedFilePaths = CreateCHMIndexFile(childNode, sBuilder, ref encryptedPaths, ref abortedPaths);
                                invalidatedFilePaths.AddRange(subInvalidatedFilePaths);
                            }
                        }

                        break;
                    }
            }
            return invalidatedFilePaths;
        }

        /// <summary>
        /// 生成CHM工程所需要的索引文件。与目录文件不同，索引文件不分层级。
        /// </summary>
        /// <param name="directoryNode">基准目录，通常应传入工作区目录。</param>
        /// <param name="prefixPath">表示目录层级关系的前缀。</param>
        /// <param name="sBuilder">文本缓存。</param>
        /// <returns>返回含有特殊字符的文件路径的列表。</returns>
        private List<string> CreateCHMIndexFile(DirectoryInfo directoryInfo, string prefixPath, StringBuilder sBuilder, ref List<string> encryptedPaths, ref List<string> abortedPaths)
        {
            if (directoryInfo == null || directoryInfo.Exists == false || prefixPath == null || sBuilder == null)
                return new List<string>();

            if (prefixPath != "" && prefixPath.EndsWith("/") == false) prefixPath += "/";

            List<string> invalidatedFilePaths = new List<string>();

            var files = directoryInfo.GetFiles();
            if (files.Length > 0)
            {
                foreach (var fileInfo in files)
                {
                    if (fileInfo.FullName.ToLower().EndsWith(".html") == false) continue;
                    if (fileInfo.FullName.ToLower().EndsWith("~.html")) continue;

                    if (ValidateFilePath(fileInfo.FullName, true) == false)
                    {
                        invalidatedFilePaths.Add(fileInfo.FullName);
                    }

                    #region 判断是否需要跳过此文件
                    if (Globals.MainWindow.IgnoreEncryptedFiles)
                    {
                        //编译工作区时忽略已加密文件
                        string password, passwordTip;
                        var isEncrypted = CustomMarkdownSupport.IsFileEncrypted(fileInfo.FullName, out password, out passwordTip);
                        if (isEncrypted)
                        {
                            encryptedPaths.Add(fileInfo.FullName);
                            break;//忽略被加密的元文档及整个子目录。
                        }
                    }

                    if (Globals.MainWindow.IgnoreAbortedFiles)
                    {
                        //编译工作区时忽略被标记为“废弃”的元文档及整个子目录。
                        if (CustomMarkdownSupport.IsFileAborted(fileInfo.FullName))
                        {
                            abortedPaths.Add(fileInfo.FullName);
                            break;
                        }
                    }
                    #endregion

                    string contentEntryName = GetTitleOfMdFile(fileInfo.FullName);

                    if (string.IsNullOrEmpty(contentEntryName))
                    {
                        var shortName = fileInfo.Name;
                        if (shortName.StartsWith("_") && shortName.Length > 1)
                        {
                            shortName = shortName.Substring(1);
                        }

                        if (shortName.ToLower() == "index.html" || shortName.ToLower() == "_index.html")
                        {
                            shortName = "目录";
                        }
                        else
                        {
                            if (shortName.ToLower().EndsWith(".html") && shortName.Length > 5)
                            {
                                shortName = shortName.Substring(0, shortName.Length - 5);
                            }
                        }

                        contentEntryName = shortName;
                    }

                    sBuilder.Append("\t<LI> <OBJECT type = \"text/sitemap\">\r\n");
                    sBuilder.Append($"\t\t<param name =\"Name\" value=\"{contentEntryName}\">\r\n");
                    sBuilder.Append($"\t\t<param name =\"Local\" value=\"{prefixPath}{CustomMarkdownSupport.UrlEncode(fileInfo.Name)}\">\r\n");
                    sBuilder.Append("\t\t</OBJECT>\r\n");
                }
            }

            var subDirectories = directoryInfo.GetDirectories();
            if (subDirectories.Length > 0)
            {
                foreach (var subDirectory in subDirectories)
                {
                    var subFiles = subDirectory.GetFiles();
                    string directoryMetaFile = null;
                    foreach (var subfile in subFiles)
                    {
                        if (subfile.FullName.ToLower().EndsWith(".html"))
                        {
                            if (subfile.Name.ToLower() == ("_" + subDirectory.Name + ".html").ToLower())
                            {
                                directoryMetaFile = subfile.Name;
                            }
                        }
                    }

                    //要判断元文件是否被加密、被标记为“废弃”
                    //如果目录元文件被标记为“废弃”，则整个目录被跳过！！！
                    var subDirectoryMetaFilePath = subDirectory.FullName + "\\" + directoryMetaFile;

                    #region 判断是否需要跳过此文件
                    if (Globals.MainWindow.IgnoreEncryptedFiles)
                    {
                        //编译工作区时忽略已加密文件
                        string password, passwordTip;
                        var isEncrypted = CustomMarkdownSupport.IsFileEncrypted(subDirectoryMetaFilePath, out password, out passwordTip);
                        if (isEncrypted)
                        {
                            encryptedPaths.Add(subDirectoryMetaFilePath);
                            continue;//忽略被加密的文档。
                        }
                    }

                    if (Globals.MainWindow.IgnoreAbortedFiles)
                    {
                        //编译工作区时忽略被标记为“废弃”的文件。
                        if (CustomMarkdownSupport.IsFileAborted(subDirectoryMetaFilePath))
                        {
                            abortedPaths.Add(subDirectoryMetaFilePath);
                            continue;
                        }
                    }
                    #endregion

                    if (subDirectory.FullName.EndsWith("~")) continue;

                    var htmlCount = 0;
                    foreach (var subfile in subFiles)
                    {
                        if (subfile.FullName.ToLower().EndsWith(".html")) htmlCount++;
                    }

                    if (htmlCount > 0)
                    {
                        //索引文件不加目录
                        //sb.Append($"\t<LI> <OBJECT type = \"text/sitemap\">\r\n");
                        //sb.Append($"\t\t<param name =\"Name\" value=\"{subDirectiry.Name}\">\r\n");
                        //sb.Append($"\t\t</OBJECT>\r\n");

                        var subPrefixPath = CustomMarkdownSupport.UrlEncode(subDirectory.FullName);

                        //因为是递归的，所以不能直接用下面这行：
                        //hasInvalidatedFilePath = CreateContentDirectory(subDirectiry, prefixTab, subPrefixPath, sb);
                        var subInvalidatedFilePaths = CreateCHMIndexFile(subDirectory, subPrefixPath, sBuilder, ref encryptedPaths, ref abortedPaths);
                        if (subInvalidatedFilePaths.Count > 0)
                        {
                            invalidatedFilePaths.AddRange(subInvalidatedFilePaths);
                        }
                    }
                }
            }

            return invalidatedFilePaths;
        }

        /// <summary>
        /// 根据指定的 Markdown 文件读取其中的标题文本。
        /// 标题文本通常是第一个以“%”开头的行，如找不到，则寻找被“标题＞＞”和“＜＜标题”包围的文本作为标题。
        /// </summary>
        public static string GetTitleOfMdFile(string mdFileFullName)
        {
            if (mdFileFullName != null && mdFileFullName.ToLower().EndsWith(".html"))
            {
                mdFileFullName = mdFileFullName.Substring(0, mdFileFullName.Length - 5) + ".md";
            }

            if (File.Exists(mdFileFullName) == false) return "";


            IEnumerable<string> lines;

            bool isEncrypted = false;
            string password, passwordTip;
            isEncrypted = CustomMarkdownSupport.IsFileEncrypted(mdFileFullName, out password, out passwordTip);
            if (isEncrypted)
            {
                var allText = File.ReadAllText(mdFileFullName);
                var contentText = SetPasswordPanel.TextDecrypt(allText.Substring(allText.IndexOf("\r\n") + 2).ToCharArray(), "DyBj#PpBb");
                lines = contentText.Split(new string[] { "\r\n" }, StringSplitOptions.None);
            }
            else
            {
                lines = File.ReadLines(mdFileFullName);
            }

            int lineIndex = 0;
            foreach (string s in lines)
            {
                if (lineIndex > 9) break;//禁止标题出现在前10行之外——如果读取整个文件来寻找个标题也太恐怖了。

                if (s.StartsWith("%"))
                {
                    //不需要考虑“title>”这样的特殊写法，因为编译前都会自动格式化成半角百分号
                    var ts = s.Substring(1).Trim();

                    if (ts.Length >= 31)
                    {
                        return ts.Substring(0, 30);
                    }
                    else return ts;
                }

                var title = CustomMarkdownSupport.GetDocumentTitle(s);
                if (string.IsNullOrWhiteSpace(title) == false) return title;

                int startIndex = s.IndexOf("标题＞＞");
                int endIndex = s.IndexOf("＜＜标题");
                if (startIndex >= 0 && endIndex >= 0)
                {
                    return s.Substring(startIndex + 4, endIndex - startIndex - 4);
                }
                lineIndex++;
            }

            return "";
        }

        private bool SaveModifiedDocuments()
        {
            List<MarkdownEditor> needSavingDocumentList = new List<MarkdownEditor>();
            foreach (var item in this.mainTabControl.Items)
            {
                MarkdownEditor eti = item as MarkdownEditor;
                if (eti != null && eti.IsModified)
                {
                    needSavingDocumentList.Add(eti);
                }
            }

            if (needSavingDocumentList.Count <= 0) return true;

            MessageBoxResult r = LMessageBox.Show(string.Format("　　有 {0} 个文档已被修改，要保存吗？", needSavingDocumentList.Count),
                Globals.AppName, MessageBoxButton.OKCancel, MessageBoxImage.Warning);
            if (r == MessageBoxResult.OK)
            {
                SaveAllDocumentsAndOptions();
                return true;
            }
            else return false;
        }

        /// <summary>
        /// 编译工作区及其下级目录下的所有 Markdown 文档。
        /// </summary>
        private void miCompileAllMdFilesOfWorkspace_Click(object sender, RoutedEventArgs e)
        {
            if (SaveModifiedDocuments() == false) return;

            if (miUtf8.IsChecked)
            {
                var result = LMessageBox.Show("　　当前选择的字符编码是UTF-8，这种编码的Html不适合编译成CHM文档。要自动改成GB2312字符编码吗？",
                                    Globals.AppName, MessageBoxButton.YesNoCancel, MessageBoxImage.Warning);

                if (result == MessageBoxResult.Cancel) return;
                if (result == MessageBoxResult.Yes)
                {
                    miGb2312.IsChecked = true;
                    miGb2312_Click(sender, e);
                }
            }

            CompileWholeWorkspace();

            var charsetText = "GB2312";
            if (miUtf8.IsChecked)
            {
                charsetText = "UTF-8";
            }

            var indexHtmlFilePath = Globals.PathOfWorkspace + "_index.html";

            if (File.Exists(indexHtmlFilePath))
            {
                if (previewFrame.Source != null && previewFrame.Source.AbsolutePath == indexHtmlFilePath)
                {
                    previewFrame.Refresh();
                }
                else
                {
                    previewFrame.Source = new Uri(indexHtmlFilePath);
                }

                tcRightToolBar.SelectedItem = tiHtmlPreview;
                if (tcRightToolBar.ActualWidth < 100)
                {
                    cdMainEditArea.Width =
                        cdRightToolsArea.Width = new GridLength(3, GridUnitType.Star);
                }
            }

            LMessageBox.Show($"　　已按照【{charsetText}】编码将工作区内所有 Markdown 文件编译为 Html 文件！",
                Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Information);
        }

        /// <summary>
        /// 编译全工作区（含下级目录）下的所有 Markdown 文件。
        /// </summary>
        private void CompileWholeWorkspace()
        {
            List<FileSystemEntry> compiledFileEntries = new List<FileSystemEntry>();

            CompileAllMdFiles(ref compiledFileEntries);

            //生成一个html索引文档。
            //BuildIndexOfCompiledHtmlFiles(fileSystemEntries);   //旧版，根据目录
            BuildIndexOfCompiledHtmlFiles();                      //新版，根据工作区管理器条目布局
        }

        /// <summary>
        /// 打开创建好的属于当前工作区的 CHM 工程文件。
        /// </summary>
        private void miOpenCHMProject_Click(object sender, RoutedEventArgs e)
        {
            if (SaveModifiedDocuments() == false) return;

            LoadHHWInstalledPath();

            if (File.Exists(this.HHWInstalledPath) == false)
            {
                LMessageBox.Show("　　未指定 Microsoft HTML Help Workshop.exe 的磁盘路径，无法调用！\r\n" +
                    "　　您可能需要到微软公司官方网站下载该工具并安装。",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Information);
                return;
            }

            var prefixOfFiles = Globals.PathOfWorkspace + Globals.WorkspaceShortName;
            var chmProjectFilePath = prefixOfFiles + ".hhp";

            if (File.Exists(chmProjectFilePath))
            {
                //System.Diagnostics.Process.Start(this.HHWInstalledPath, $"\"{chmProjectFilePath}\"");
                //加引号的办法虽然可以解决路径带空格的问题，但在 Html Help Workshop 中点击“编译”按钮时
                //在弹出的路径选取框中获得的路径也会带双引号——这时又会弹出“找不到路径”的错误框。

                //下面这个办法也不行，这貌似是 Linux 下的办法？
                //if (chmProjectFilePath.Contains(" "))
                //{
                //    chmProjectFilePath = chmProjectFilePath.Replace(" ", "\\ ");
                //}

                //事实证明，给每个子目录或文件名（带空格）分别包围双引号的办法也行不通。
                //只能禁止工作区目录和用户创建的所有目录带空格了，还要提示用户选择的路径带空格。

                if (chmProjectFilePath.Contains(" "))
                {
                    LMessageBox.Show("　　您设置的工作区目录完整路径中含有空格，这会导致 Html Help Workshop 在编译时提示无法打开工程文件！\r\n　　请在弹出该消息框后手工选取工程文件位置再编译（办法是去除路径首尾自动添加的双引号）。",
                        Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning, "", "001_常见问题与错误.html#can_not_find_chm_project");
                    System.Diagnostics.Process.Start(this.HHWInstalledPath, $"\"{chmProjectFilePath}\"");
                }
                else
                {
                    System.Diagnostics.Process.Start(this.HHWInstalledPath, chmProjectFilePath);
                }
            }
            else
            {
                LMessageBox.Show("　　尚未创建工程。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        /// <summary>
        /// 直接编译创建好的属于当前工作区的 CHM 工程文件。
        /// </summary>
        private void miCompileCHMProject_Click(object sender, RoutedEventArgs e)
        {
            if (SaveModifiedDocuments() == false) return;

            //每次编译前都重新创建CHM工程文件
            //CreateChmProjectFiles() 方法会自动调用 WorkspaceManager.SaveWorkspaceTreeviewToXml();
            if (CreateChmProjectFiles() == false) return;

            LoadHHCInstalledPath();

            if (File.Exists(this.HHCInstalledPath) == false)
            {
                LMessageBox.Show("　　未指定 Microsoft HTML Help Workshop 的编译器程序（hhc.exe）的磁盘路径，无法调用！\r\n" +
                    "　　您可能需要到微软公司官方网站下载该工具并安装。",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Information);
                return;
            }

            try
            {
                //修改功能，创建工程文件之前已询问过了。2017年7月20日
                //var result = LMessageBox.Show("　　编译 CHM 工程之前需要重新编译当前工作区中的 Markdown 文件吗？", Globals.AppName,
                //     MessageBoxButton.YesNo, MessageBoxImage.Question);
                //if (result == MessageBoxResult.Yes)
                //{
                //    miCompileAllMdFilesOfWorkspace_Click(sender, e);
                //}

                var prefixOfFiles = Globals.PathOfWorkspace + Globals.WorkspaceShortName;
                var chmProjectFilePath = prefixOfFiles + ".hhp";

                if (File.Exists(chmProjectFilePath))
                {
                    //System.Diagnostics.Process.Start(this.HHWInstalledPath, $"\"{chmProjectFilePath}\"");
                    //加引号的办法虽然可以解决路径带空格的问题，但在 Html Help Workshop 中点击“编译”按钮时
                    //在弹出的路径选取框中获得的路径也会带双引号——这时又会弹出“找不到路径”的错误框。

                    //下面这个办法也不行，这貌似是 Linux 下的办法？
                    //if (chmProjectFilePath.Contains(" "))
                    //{
                    //    chmProjectFilePath = chmProjectFilePath.Replace(" ", "\\ ");
                    //}

                    //事实证明，给每个子目录或文件名（带空格）分别包围双引号的办法也行不通。
                    //只能禁止工作区目录和用户创建的所有目录带空格了，还要提示用户选择的路径带空格。

                    System.Diagnostics.Process process;

                    if (chmProjectFilePath.Contains(" "))
                    {
                        process = System.Diagnostics.Process.Start(this.HHCInstalledPath, $"\"{chmProjectFilePath}\"");
                    }
                    else
                    {
                        process = System.Diagnostics.Process.Start(this.HHCInstalledPath, chmProjectFilePath);
                    }

                    if (process != null)
                    {
                        process.EnableRaisingEvents = true;
                        process.Exited += CompileChmProjectProcess_Exited;
                    }
                }
                else
                {
                    LMessageBox.Show("　　尚未创建工程。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                }
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        private void CompileChmProjectProcess_Exited(object sender, EventArgs e)
        {
            miOpenChmFile_Click(sender, null);
        }

        /// <summary>
        /// 清理工作区及其下级目录中所有 Html 文件（无论是不是由 LME 生成的）。
        /// </summary>
        private void miClearCompiledHtmlFiles_Click(object sender, RoutedEventArgs e)
        {
            var result = LMessageBox.Show("　　此功能将删除工作区目录下所有Html文件，无论它是不是由本程序编译而成！\r\n\r\n　　真的要继续吗？",
                Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Warning);

            if (result != MessageBoxResult.Yes) return;

            DeleteHtmlFiles(Globals.PathOfWorkspace);
        }

        /// <summary>
        /// 清理指定目录及其下级目录中所有 Html 文件（无论是不是由 LME 生成的）。
        /// </summary>
        /// <param name="directoryPath">基准目录。从此目录开始递归清理。</param>
        private void DeleteHtmlFiles(string directoryPath)
        {
            if (Directory.Exists(directoryPath) == false) return;
            var directoryShortName = Path.GetDirectoryName(directoryPath);
            if (directoryShortName.EndsWith("~")) return;

            var directoryInfo = new DirectoryInfo(directoryPath);
            var files = directoryInfo.GetFiles();
            foreach (var file in files)
            {
                if (file.Extension.ToLower() != ".html") continue;
                if (Path.GetFileNameWithoutExtension(file.FullName).EndsWith("~")) continue;

                File.Delete(file.FullName);
            }

            var subDirectiries = directoryInfo.GetDirectories();
            foreach (var subDirectory in subDirectiries)
            {
                DeleteHtmlFiles(subDirectory.FullName);
            }
        }

        /// <summary>
        /// 切换“填空模式（FillBlank）”的开关。此属性用以决定是否将行内代码片段编译为带填空题的效果。
        /// 即是否令 Html 的 <code></code> 块初始前景色为透明——用户点击后再更改为其它色彩。
        /// </summary>
        private void miFillblankMode_Click(object sender, RoutedEventArgs e)
        {
            this.compileCodeToFillBlank =
            miFillblankMode.IsChecked = !miFillblankMode.IsChecked;
            App.WorkspaceConfigManager.Set("CompileCodeToFillBlank", miFillblankMode.IsChecked.ToString());
        }

        /// <summary>
        /// 删除历史工作区列表中选定条目。
        /// </summary>
        private void miDeleteHistoryWorkspaceEntry_Click(object sender, RoutedEventArgs e)
        {
            if (lbxHistoryWorkspaces.SelectedIndex < 0)
            {
                LMessageBox.Show("　　请先选择要删除的条目！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            lbxHistoryWorkspaces.Items.RemoveAt(lbxHistoryWorkspaces.SelectedIndex);

            StringBuilder sb = new StringBuilder();
            foreach (RecentDirectoryListBoxItem item in lbxHistoryWorkspaces.Items)
            {
                sb.Insert(0, "\r\n" + item.DirectoryPath);
            }

            File.WriteAllText(Globals.PathOfHistoryWorkspaceFileFullName, sb.ToString(), new UnicodeEncoding());
        }

        /// <summary>
        /// 删除历史导出区列表中选定的目录条目。
        /// </summary>
        private void miDeleteHistoryOutputEntry_Click(object sender, RoutedEventArgs e)
        {
            if (lbxHistoryOutport.SelectedIndex < 0)
            {
                LMessageBox.Show("　　请先选择要删除的条目！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            lbxHistoryOutport.Items.RemoveAt(lbxHistoryOutport.SelectedIndex);

            StringBuilder sb = new StringBuilder();
            foreach (RecentDirectoryListBoxItem item in lbxHistoryOutport.Items)
            {
                sb.Insert(0, "\r\n" + item.DirectoryPath);
            }

            File.WriteAllText(Globals.PathOfHistoryOutputFileFullName, sb.ToString(), new UnicodeEncoding());
        }

        /// <summary>
        /// 清除“历史工作区（最近工作区列）”列表。
        /// </summary>
        private void miDeleteHistoryWorkspaceFile_Click(object sender, RoutedEventArgs e)
        {
            if (lbxHistoryWorkspaces.Items.Count <= 0) return;

            var result = LMessageBox.Show("　　真的要清除“最近工作区列表”吗？", Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Question);
            if (result != MessageBoxResult.Yes) return;

            try
            {
                if (File.Exists(Globals.PathOfHistoryWorkspaceFileFullName))
                {
                    File.Delete(Globals.PathOfHistoryWorkspaceFileFullName);
                }

                lbxHistoryWorkspaces.Items.Clear();
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        /// <summary>
        /// 清除“历史导出区”列表。
        /// </summary>
        private void miDeleteHistoryOutputFile_Click(object sender, RoutedEventArgs e)
        {
            if (lbxHistoryOutport.Items.Count <= 0) return;

            var result = LMessageBox.Show("　　真的要清除“历史导出目录列表”吗？",
                Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Question);
            if (result != MessageBoxResult.Yes) return;

            try
            {
                if (File.Exists(Globals.PathOfHistoryOutputFileFullName))
                {
                    File.Delete(Globals.PathOfHistoryOutputFileFullName);
                }

                lbxHistoryOutport.Items.Clear();
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName,
                    MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        /// <summary>
        /// 在当前活动编辑器当前位置插入一个“锚”。形如：[](@)。
        /// </summary>
        private void miInsertAnchor_Click(object sender, RoutedEventArgs e)
        {
            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            var selText = eti.EditorBase.SelectedText.Replace("\r", "").Replace("\n", "").Replace(" ", "-").Replace("　", "-").Replace("\t", "-");
            if (selText.Length > 0)
            {
                eti.EditorBase.SelectedText = $"\r\n\r\n[](@{ChinesePinYin.ToChinesePinYinText(selText)} {selText})\r\n\r\n";
                var destSel = eti.EditorBase.SelectionStart + 8;
                if (destSel < eti.EditorBase.Document.TextLength)
                {
                    eti.EditorBase.Select(destSel, selText.Length);
                }
            }
            else
            {
                eti.EditorBase.SelectedText = "\r\n\r\n[](@请输入锚名称)\r\n\r\n";
                var destSel = eti.EditorBase.SelectionStart + 8;
                if (destSel < eti.EditorBase.Document.TextLength)
                {
                    eti.EditorBase.Select(destSel, 6);
                }
            }
        }

        /// <summary>
        /// 在当前活动编辑器当前位置的前一行插入一个“锚”。形如：[](@)。
        /// </summary>
        private void miInsertAnchorAtPreviewLine_Click(object sender, RoutedEventArgs e)
        {
            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            var line = eti.EditorBase.Document.GetLineByOffset(eti.EditorBase.SelectionStart);

            var selText = eti.EditorBase.SelectedText.Replace("\r", "").Replace("\n", "").Replace(" ", "-").Replace("　", "-").Replace("\t", "-");
            if (selText.Length > 0)
            {
                var anchorText = $"\r\n\r\n[](@{ChinesePinYin.ToChinesePinYinText(selText)} {selText})\r\n\r\n";
                eti.EditorBase.BeginChange();
                eti.EditorBase.Document.Replace(line.Offset, 0, anchorText);
                eti.EditorBase.EndChange();
            }
            else
            {
                var anchorText = "\r\n\r\n[](@请输入锚名称)\r\n\r\n";
                eti.EditorBase.BeginChange();
                eti.EditorBase.Document.Replace(line.Offset, 0, anchorText);
                eti.EditorBase.EndChange();
                var destSel = line.Offset + 8;
                if (destSel > 0)
                {
                    eti.EditorBase.Select(destSel, 6);
                }
            }
        }

        /// <summary>
        /// 在当前活动编辑器当前位置插入链接标记文本。形如：[]()。
        /// </summary>
        private void miInsertLinkMark_Click(object sender, RoutedEventArgs e)
        {
            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            var selText = eti.EditorBase.SelectedText;
            if (selText.Length > 0)
            {
                eti.EditorBase.SelectedText = $"[{selText}]()";
                var destSel = eti.EditorBase.SelectionStart + 3 + selText.Length;
                if (destSel < eti.EditorBase.Document.TextLength)
                {
                    eti.EditorBase.Select(destSel, 0);
                }
            }
            else
            {
                eti.EditorBase.SelectedText = "[请输入链接名称]()";
                var destSel = eti.EditorBase.SelectionStart + 1;
                if (destSel < eti.EditorBase.Document.TextLength)
                {
                    eti.EditorBase.Select(destSel, 7);
                }
            }
        }

        /// <summary>
        /// 窗口最大化。
        /// </summary>
        private void miMaxSizeWindow_Click(object sender, RoutedEventArgs e)
        {
            this.WindowState = WindowState.Maximized;
        }

        /// <summary>
        /// 切换“自动折行（TextAutoWrap）”的开关。开启时编辑区文本过长时会自动折行，否则总是在一行（显示横向滚动条）。
        /// </summary>
        private void miTextWrap_Click(object sender, RoutedEventArgs e)
        {
            this.TextAutoWrap =
            this.miTextAutoWrap.IsChecked = !this.miTextAutoWrap.IsChecked;
            App.ConfigManager.Set("TextAutoWrap", this.TextAutoWrap.ToString());

            RefreshTextAutoWrapToStatusBar();
        }

        /// <summary>
        /// 打开工作区管理器中选定条目所对应的 Markdown 文件、图像文件、目录。
        /// 图像文件、目录会调用 Windows 资源管理器打开；Markdown 文件则在右侧编辑区打开。
        /// </summary>
        private void miOpenFileOrFolder_Click(object sender, RoutedEventArgs e)
        {
            if (tvWorkDirectory.SelectedItem == null)
            {
                LMessageBox.Show("　　请先选中工作区目录中某一个条目！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            WorkspaceTreeViewItem wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (wtvi.IsMarkdownFilePath)
            {
                OpenDocuments(new string[] { wtvi.FullPath });
            }
            else if (wtvi.IsDirectoryExists || wtvi.IsImageFileExist)
            {
                System.Diagnostics.Process.Start("explorer.exe", $"\"{wtvi.FullPath}\"");
            }
            else
            {
                LMessageBox.Show("　　只有Markdown文档或目录或图像文件才可以打开！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        /// <summary>
        /// 打开工作区管理器中选定的 Markdown 文档。
        /// 如果选中的条目是指向一个目录（但不是资源目录）且尚未创建该目录的元文件，则自动创建一个目录元文件。
        /// </summary>
        private void miOpenDocument_Click(object sender, RoutedEventArgs e)
        {
            if (tvWorkDirectory.SelectedItem == null)
            {
                LMessageBox.Show("　　请先选中工作区目录中某一个条目！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            //如果是MD文件，直接打开；
            //如果是Directory，打开它的默认MD文件；
            //如果是Image，调用Explorer使用系统默认图片应用程序打开。

            WorkspaceTreeViewItem wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (wtvi.IsMarkdownFilePath)
            {
                OpenDocuments(new string[] { wtvi.FullPath });
            }
            else if (wtvi.IsDirectoryExists)
            {
                try
                {
                    DirectoryInfo di = new DirectoryInfo(wtvi.FullPath);
                    if (di.Name.EndsWith("~"))
                    {
                        LMessageBox.Show("　　以波型符结尾的是资源文件夹，不允许创建对应 Markdown 元文件。", Globals.AppName,
                          MessageBoxButton.OK, MessageBoxImage.Warning);
                        return;
                    }
                    var directoryMdFileFullName = (di.FullName.EndsWith("\\") ? di.FullName : (di.FullName + "\\")) + "_" + di.Name + ".md";
                    CreateDirectoryMetaMdFile(di, directoryMdFileFullName);

                    OpenDocuments(new string[] { directoryMdFileFullName });
                }
                catch (Exception ex)
                {
                    LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace);
                }
            }
            else if (wtvi.IsImageFileExist)
            {
                System.Diagnostics.Process.Start("explorer.exe", $"\"{wtvi.FullPath}\"");
            }
        }

        /// <summary>
        /// 创建目录元文件。所谓目录元文件是为了 chm 提供的，创建 chm 目录时，会链接到目录而不是链接到目录下的文件条目。
        /// </summary>
        /// <param name="di">目录信息。</param>
        /// <param name="directoryMdFileFullName">目录对应元文件的路径。应该在该目录下并以“_目录名.md”为文件短名。</param>
        private static void CreateDirectoryMetaMdFile(DirectoryInfo di, string directoryMdFileFullName)
        {
            if (File.Exists(directoryMdFileFullName) == false)
            {
                using (StreamWriter sw = File.CreateText(directoryMdFileFullName))
                {
                    sw.Write($"\r\n%{FormatDocumentTitle(di.Name)}\r\n\r\n；{DateTime.Now.ToString()}");
                }
            }
        }

        /// <summary>
        /// 调用 Windows Explorer（资源管理器）打开当前工作区中选定的条目所在的目录。
        /// </summary>
        private void miOpenFolder_Click(object sender, RoutedEventArgs e)
        {
            if (tvWorkDirectory.SelectedItem == null)
            {
                LMessageBox.Show("　　请先选中工作区目录中某一个条目！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            //如果是MD文件或图片，调用Explorer打开它所在的目录;
            //如果是目录本身，调用Explorer，直接打开该目录。
            WorkspaceTreeViewItem wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;

            if (wtvi.IsMarkdownFilePath || wtvi.IsImageFileExist)
            {
                FileInfo fi = new FileInfo(wtvi.FullPath);
                System.Diagnostics.Process.Start("explorer.exe", $"\"{fi.Directory.FullName}\"");
            }
            else if (wtvi.IsDirectoryExists)
            {
                System.Diagnostics.Process.Start("explorer.exe", $"\"{wtvi.FullPath}\"");
            }
        }

        /// <summary>
        /// 根据当前工作区中选择的条目创建一个同级目录。
        /// </summary>
        private void miNewBrotherDirectory_Click(object sender, RoutedEventArgs e)
        {
            if (tvWorkDirectory.SelectedItem == null)
            {
                LMessageBox.Show("　　请在工作区中选择一个目录或Markdown文件再执行此操作。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            WorkspaceTreeViewItem wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            DirectoryInfo di = new DirectoryInfo(wtvi.FullPath);
            if (di.Name.EndsWith("~"))
            {
                LMessageBox.Show("　　以波形符结尾的目录是程序自动管理的资源目录，不允许在其下新建子目录。",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            string parentDirectory = null;
            if (Directory.Exists(wtvi.FullPath))
            {
                parentDirectory = new DirectoryInfo(wtvi.FullPath).Parent.FullName;
            }
            else if (File.Exists(wtvi.FullPath))
            {
                if (wtvi.IsMarkdownFilePath)
                {
                    parentDirectory = new FileInfo(wtvi.FullPath).Directory.FullName;
                }
            }

            if (parentDirectory == null)
            {
                LMessageBox.Show("　　请在工作区中选择一个目录或Markdown文件再执行此操作。",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            var parentItem = wtvi.ParentWorkspaceTreeViewItem;

            if (wtvi.FullPath.ToLower() == Globals.PathOfWorkspace.ToLower())
            {
                LMessageBox.Show("　　不允许建立与工作区同级的子目录，新目录将会被包含在工作区目录中。",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                parentDirectory = Globals.PathOfWorkspace;
                parentItem = wtvi;//工作区目录比较特殊
            }

            var newShortDirectoryName = InputBox.Show(Globals.AppName, "请输入新目录名（不能以“_”开头，尽量以字母开头）：", "", true);
            if (string.IsNullOrEmpty(newShortDirectoryName))
            {
                LMessageBox.Show("　　目录名称不能为空！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }

            var newShortCommentName = newShortDirectoryName;

            newShortDirectoryName = ChinesePinYin.ToChinesePinYinText(newShortCommentName);

            try
            {
                var newFullPath = (parentDirectory.EndsWith("\\") ? parentDirectory : (parentDirectory + "\\")) + newShortDirectoryName;
                if (Directory.Exists(newFullPath) == false)
                {
                    Directory.CreateDirectory(newFullPath);
                    var destDirectoryInfo = new DirectoryInfo(newFullPath);

                    //自动创建目录元文件，不必再双击了。

                    Regex headerNumberRegex = new Regex(@"^[lL]\d{7,}[ 　-]");
                    var matchHeaderNumber = headerNumberRegex.Match(newShortCommentName);
                    if (matchHeaderNumber.Success)
                    {
                        newShortCommentName = newShortCommentName.Substring(matchHeaderNumber.Length);
                    }

                    using (var stream = File.CreateText(destDirectoryInfo.FullName + "\\" + "_" + destDirectoryInfo.Name + ".md"))
                    {
                        stream.Write($"\r\n%{/*FormatDocumentTitle(*/newShortCommentName/*)*/}\r\n\r\n；{DateTime.Now.ToString()}\r\n\r\n");
                    }
                }
                else
                {
                    LMessageBox.Show("　　已存在指定名称的目录，操作无法完成。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Error);
                    return;
                }

                var newItem = new WorkspaceTreeViewItem(newFullPath, Globals.MainWindow);
                if (parentItem != null)
                {
                    //parentItem.Items.Add(newItem);

                    //2017年7月21日，现在已不必再排序，由用户决定。
                    //添加同级节点时，在当前节点下方，添加下级时，直接添加到下级最后一个。
                    #region 废弃代码
                    //List<WorkspaceTreeViewItem> tmpItems = new List<WorkspaceTreeViewItem>();
                    //int baseIndex = 0;
                    //foreach (var item in parentItem.Items)
                    //{
                    //    var wi = item as WorkspaceTreeViewItem;
                    //    if (wi != null)
                    //    {
                    //        if (wi.ShortName.EndsWith("~"))//波型符结尾的总是资源文件夹。
                    //        {
                    //            baseIndex++;
                    //            continue;
                    //        }
                    //        tmpItems.Add(wi);
                    //    }
                    //}
                    //tmpItems.Add(newItem);
                    //tmpItems.Sort(new WorkspaceTreeViewItemCompare());
                    //var index = tmpItems.IndexOf(newItem) + baseIndex;
                    //parentItem.Items.Insert(index, newItem); 
                    #endregion

                    var index = parentItem.Items.IndexOf(wtvi);
                    parentItem.Items.Insert(index + 1, newItem);
                }
                newItem.IsSelected = true;
                workspaceManager.SaveWorkspaceTreeviewToXml();
            }
            catch (Exception ex)
            {
                LMessageBox.Show("　　创建目录失败！错误消息：\r\n" + ex.Message + "\r\n" + ex.StackTrace,
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Error);

            }
        }

        /// <summary>
        /// 查找任务列表。
        /// </summary>
        /// <param name="searchHeader">[-]表示未开始；[%]表示正在进行；[+]表示已完成；![+]表示未完成；[#]表示已废弃。</param>
        public void FindTaskList(string searchHeader, TreeView treeView)
        {
            if (treeView == null) treeView = tvTaskList;

            treeView.Items.Clear();
            FindTaskList((cmbSearchArea.SelectedItem as ComboBoxItem).Tag.ToString(), searchHeader, treeView);

            if (treeView.Items.Count <= 0)
            {
                treeView.Items.Add(new TreeViewItem() { Header = "<没找到任务列表...>", });
            }

            cmbFindText.Items.Add(cmbFindText.Text);

            if (tcRightToolBar.SelectedItem != tiTaskList)
            {
                tcRightToolBar.SelectedItem = tiTaskList;
            }

            if (cdRightToolsArea.ActualWidth < 140)
            {
                cdRightToolsArea.Width = new GridLength(2, GridUnitType.Star);
            }
        }

        /// <summary>
        /// 在磁盘文档或当前正在编辑的文档之间查找指定文本内容。
        /// </summary>
        /// <param name="searchArea">搜索的文档的范围（当前文档、打开的文档、全工作区）。</param>
        /// <param name="searchHeader">查找的任务项目的类型（[-]，未开始；[%]正在进行；[+]已完成；[#]已废弃。</param>
        private void FindTaskList(string searchArea, string searchHeader, TreeView treeView)
        {
            if (treeView == null) treeView = tvTaskList;

            treeView.Items.Clear();
            switch (searchArea)
            {
                case "ActiveDocument":
                    {
                        FindTaskListInActiveDocument(searchHeader, treeView);
                        break;
                    }
                case "OpenedDocuments":
                    {
                        FindTaskListInOpenedDocuments(searchHeader, treeView);
                        break;
                    }
                case "AllFiles":
                    {
                        //这个需要用到递归
                        FindTaskListInAllFiles(Globals.PathOfWorkspace, searchHeader, treeView);
                        break;
                    }
            }
        }

        /// <summary>
        /// 在打开的所有文档中查找任务列表。
        /// </summary>
        /// <param name="searchHeader">用以指定要查找的任务列表的类型。
        /// 应传入下列值之一：“[-]”、“[%]”、“[+]”、“![+]”、“[#]”、“”</param>
        /// <param name="treeView">指定将查找结果显示在哪个树型框中。</param>
        private void FindTaskListInOpenedDocuments(string searchHeader, TreeView treeView)
        {
            foreach (var item in this.mainTabControl.Items)
            {
                var efi = item as MarkdownEditor;
                if (efi != null)
                {
                    FindDocumentTreeViewItem fdi = new FindDocumentTreeViewItem(efi.FullFilePath, efi.ShortFileName);
                    //为了排序，添加一个列表。
                    List<FindTaskListItem> tmpList = new List<FindTaskListItem>();

                    var lines = efi.EditorBase.Document.Lines;
                    for (int i = 1; i < lines.Count; i++)//第01行是文件状态，不属于正常的任务列表，忽略。
                    {
                        var line = lines[i];
                        var lineText = efi.EditorBase.Document.GetText(line.Offset, line.Length);
                        if (string.IsNullOrEmpty(lineText)) continue;

                        var tmp = lineText.Replace(" ", "").Replace("\t", "").Replace("　", "").Replace("：", ":");
                        bool isTaskListItem = false;
                        Brush foreColor = null;
                        if (tmp.StartsWith("[-]"))
                        {
                            isTaskListItem = true;
                            foreColor = Brushes.Red;
                        }
                        else if (tmp.StartsWith("[%]"))
                        {
                            isTaskListItem = true;
                            foreColor = Brushes.Green;
                        }
                        else if (tmp.StartsWith("[+]"))
                        {
                            isTaskListItem = true;
                            foreColor = Brushes.Blue;
                        }
                        else if (tmp.StartsWith("[#]"))
                        {
                            isTaskListItem = true;
                            foreColor = Brushes.Brown;
                        }

                        if (isTaskListItem)
                        {
                            bool isSearchItem = false;
                            if (string.IsNullOrEmpty(searchHeader))
                            {
                                isSearchItem = true;
                            }
                            else if (tmp.StartsWith(searchHeader))
                            {
                                isSearchItem = true;
                            }
                            else
                            {
                                if (searchHeader == "![+]")
                                {
                                    if (tmp.StartsWith("[-]") || tmp.StartsWith("[%]") || tmp.StartsWith("[#]"))
                                    {
                                        isSearchItem = true;
                                    }
                                }
                            }

                            if (isSearchItem)
                            {
                                int preIndex, nextIndex;
                                preIndex = lineText.IndexOf('[') + 1;
                                nextIndex = lineText.IndexOf(']') - 1;
                                TextDecorationCollection textDecoration = null;
                                if (tmp.StartsWith("[#]") || tmp.StartsWith("[+]"))
                                {
                                    textDecoration = TextDecorations.Strikethrough;
                                }

                                FindTaskListItem newItem;
                                if (preIndex >= 0 && nextIndex >= 0 && nextIndex >= preIndex)
                                {
                                    newItem = new FindTaskListItem(efi.FullFilePath, efi.ShortFileName, line.LineNumber, preIndex, nextIndex, lineText, foreColor,
                                         FindLineTreeViewItem.ItemType.TaskListItem, textDecoration);
                                    tmpList.Add(newItem);
                                }
                                else
                                {
                                    newItem = new FindTaskListItem(efi.FullFilePath, efi.ShortFileName, line.LineNumber, 0, nextIndex, lineText, foreColor,
                                         FindLineTreeViewItem.ItemType.TaskListItem, textDecoration);
                                    tmpList.Add(newItem);
                                }

                                for (int j = i + 1; j < lines.Count; j++)
                                {
                                    var line2 = lines[j];
                                    var line2Text = efi.EditorBase.Document.GetText(line2.Offset, line2.Length);
                                    if (CustomMarkdownSupport.IsTaskLine(line2Text)) break;//到下一个任务列表项就停止。

                                    if (CustomMarkdownSupport.IsDateLine(line2Text))
                                    {
                                        var leftIndex = line2Text.IndexOf("[");
                                        var rightIndex = line2Text.IndexOf("]");
                                        if (leftIndex >= 0 && rightIndex > leftIndex)
                                        {
                                            var newDateItem = new FindTaskListTimeTagItem(efi.FullFilePath, efi.ShortFileName,
                                                line2.LineNumber, leftIndex + 1, rightIndex - leftIndex - 1, line2Text, foreColor, FindLineTreeViewItem.ItemType.Normal);
                                            newItem.Items.Add(newDateItem);
                                        }
                                        else
                                        {
                                            var newDateItem = new FindTaskListTimeTagItem(efi.FullFilePath, efi.ShortFileName,
                                                line2.LineNumber, 0, 0, line2Text, foreColor, FindLineTreeViewItem.ItemType.Normal);
                                            newItem.Items.Add(newDateItem);
                                        }
                                    }
                                }

                                newItem.UpdateDateTime();
                                newItem.IsExpanded = true;
                            }
                        }
                    }

                    if (rbtnSortAsStartTime.IsChecked == true)
                    {
                        tmpList.Sort(new FindTaskListItemTimeStartCompare());
                    }
                    else if (rbtnSortAsEndTime.IsChecked == true)
                    {
                        tmpList.Sort(new FindTaskListItemTimeEndCompare());
                    }

                    foreach (var tmpItem in tmpList)
                    {
                        fdi.Items.Add(tmpItem);
                    }

                    if (fdi.Items.Count > 0)
                    {
                        treeView.Items.Add(fdi);
                        fdi.IsExpanded = true;
                        (fdi.Items[0] as TreeViewItem).IsSelected = true;
                    }
                }
            }
        }

        /// <summary>
        /// 在当前活动编辑器中查找任务列表。
        /// </summary>
        /// <param name="searchHeader">用以指定要查找的任务列表的类型。
        /// 应传入下列值之一：“[-]”、“[%]”、“[+]”、“![+]”、“[#]”、“”</param>
        /// <param name="treeView">指定将查找结果显示在哪个树型框中。</param>
        private void FindTaskListInActiveDocument(string searchHeader, TreeView treeView)
        {
            var efi = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (efi != null)
            {
                FindDocumentTreeViewItem fdi = new FindDocumentTreeViewItem(efi.FullFilePath, efi.ShortFileName);
                //为了排序，添加一个列表。
                List<FindTaskListItem> tmpList = new List<FindTaskListItem>();

                var lines = efi.EditorBase.Document.Lines;
                for (int i = 1; i < lines.Count; i++)//第01行是文件状态，不属于正常的任务列表，忽略。
                {
                    var line = lines[i];
                    var lineText = efi.EditorBase.Document.GetText(line.Offset, line.Length);
                    if (string.IsNullOrEmpty(lineText)) continue;

                    var tmp = lineText.Replace(" ", "").Replace("\t", "").Replace("　", "").Replace("：", ":");
                    bool isTaskListItem = false;
                    Brush foreground = null;
                    if (tmp.StartsWith("[-]"))
                    {
                        isTaskListItem = true;
                        foreground = Brushes.Red;
                    }
                    else if (tmp.StartsWith("[%]"))
                    {
                        isTaskListItem = true;
                        foreground = Brushes.Green;
                    }
                    else if (tmp.StartsWith("[+]"))
                    {
                        isTaskListItem = true;
                        foreground = Brushes.Blue;
                    }
                    else if (tmp.StartsWith("[#]"))
                    {
                        isTaskListItem = true;
                        foreground = Brushes.Brown;
                    }

                    if (isTaskListItem)
                    {
                        bool isSearchItem = false;
                        if (string.IsNullOrEmpty(searchHeader))
                        {
                            isSearchItem = true;
                        }
                        else if (tmp.StartsWith(searchHeader))
                        {
                            isSearchItem = true;
                        }
                        else
                        {
                            if (searchHeader == "![+]")
                            {
                                if (tmp.StartsWith("[-]") || tmp.StartsWith("[%]") || tmp.StartsWith("[#]"))
                                {
                                    isSearchItem = true;
                                }
                            }
                        }

                        if (isSearchItem)
                        {
                            int preIndex, nextIndex;
                            preIndex = lineText.IndexOf('[') + 1;
                            nextIndex = lineText.IndexOf(']') - 1;
                            TextDecorationCollection textDecoration = null;
                            if (tmp.StartsWith("[#]") || tmp.StartsWith("[+]"))
                            {
                                textDecoration = TextDecorations.Strikethrough;
                            }

                            FindTaskListItem newItem;
                            if (preIndex >= 0 && nextIndex >= 0 && nextIndex >= preIndex)
                            {
                                newItem = new FindTaskListItem(efi.FullFilePath, efi.ShortFileName, line.LineNumber, preIndex, nextIndex, lineText, foreground,
                                     FindLineTreeViewItem.ItemType.TaskListItem, textDecoration);
                                tmpList.Add(newItem);
                            }
                            else
                            {
                                newItem = new FindTaskListItem(efi.FullFilePath, efi.ShortFileName, line.LineNumber, 0, nextIndex, lineText, foreground,
                                     FindLineTreeViewItem.ItemType.TaskListItem, textDecoration);
                                tmpList.Add(newItem);
                            }

                            for (int j = i + 1; j < lines.Count; j++)
                            {
                                var line2 = lines[j];
                                var line2Text = efi.EditorBase.Document.GetText(line2.Offset, line2.Length);
                                if (CustomMarkdownSupport.IsTaskLine(line2Text)) break;//到下一个任务列表项就停止。

                                if (CustomMarkdownSupport.IsDateLine(line2Text))
                                {
                                    var leftIndex = line2Text.IndexOf("[");
                                    var rightIndex = line2Text.IndexOf("]");
                                    if (leftIndex >= 0 && rightIndex > leftIndex)
                                    {
                                        var newDateItem = new FindTaskListTimeTagItem(efi.FullFilePath, efi.ShortFileName,
                                            line2.LineNumber, leftIndex + 1, rightIndex - leftIndex - 1, line2Text, foreground, FindLineTreeViewItem.ItemType.Normal);
                                        newItem.Items.Add(newDateItem);
                                    }
                                    else
                                    {
                                        var newDateItem = new FindTaskListTimeTagItem(efi.FullFilePath, efi.ShortFileName,
                                            line2.LineNumber, 0, 0, line2Text, foreground, FindLineTreeViewItem.ItemType.Normal);
                                        newItem.Items.Add(newDateItem);
                                    }
                                }
                            }

                            newItem.UpdateDateTime();
                            newItem.IsExpanded = true;
                        }
                    }
                }

                if (rbtnSortAsStartTime.IsChecked == true)
                {
                    tmpList.Sort(new FindTaskListItemTimeStartCompare());
                }
                else if (rbtnSortAsEndTime.IsChecked == true)
                {
                    tmpList.Sort(new FindTaskListItemTimeEndCompare());
                }

                foreach (var tmpItem in tmpList)
                {
                    fdi.Items.Add(tmpItem);
                }

                if (fdi.Items.Count > 0)
                {
                    treeView.Items.Add(fdi);
                    fdi.IsExpanded = true;
                    (fdi.Items[0] as TreeViewItem).IsSelected = true;
                }
            }
        }

        /// <summary>
        /// 根据指定的文件路径查找当前有没有哪个编辑器正在编辑这个文件。
        /// </summary>
        /// <param name="mdFileFullName">Markdown 文件的磁盘路径。</param>
        /// <returns>返回正在编辑指定 Markdown 文件的编辑器。</returns>
        private MarkdownEditor GetOpenedEditor(string mdFileFullName)
        {
            if (string.IsNullOrWhiteSpace(mdFileFullName)) return null;

            if (this.mainTabControl.Items.Count > 0)
            {
                foreach (var ue in this.mainTabControl.Items)
                {
                    var editor = ue as MarkdownEditor;
                    if (editor == null) continue;
                    if (editor.FullFilePath == null) continue;

                    if (editor.FullFilePath.ToLower() == mdFileFullName.ToLower()) return editor;
                }
            }

            return null;
        }

        /// <summary>
        /// 递归查找指定目录下的所有 Markdown 文件中定义的所有任务列表项，将将查找结果显示在指定的树型框中。
        /// </summary>
        /// <param name="directoryPath">要查找的目录</param>
        /// <param name="searchHeader">要查找的任务列表的类型（[-]为未开始；[%]为正在进行；[+]是已完成；[#]是已废弃。）</param>
        /// <param name="treeView">指定要将查找结果显示在哪个树型框中。</param>
        private void FindTaskListInAllFiles(string directoryPath, string searchHeader, TreeView treeView)
        {
            if (Directory.Exists(directoryPath) == false) return;
            var directory = new DirectoryInfo(directoryPath);

            //先处理当前目录下的文件
            var childFilesInfos = directory.GetFiles();

            foreach (var childFileInfo in childFilesInfos)
            {
                if (childFileInfo.FullName.ToLower().EndsWith(".md") == false) continue;

                string[] lines;

                //已打开的文件，按打开的情况算，没打开的，按磁盘文本查找。
                var fileEditor = GetOpenedEditor(childFileInfo.FullName);
                if (fileEditor != null)
                {
                    lines = fileEditor.EditorBase.Text.Replace("\r", "").Split(new char[] { '\n' });
                }
                else
                {
                    lines = File.ReadAllLines(childFileInfo.FullName);
                }

                FindDocumentTreeViewItem fdi = new FindDocumentTreeViewItem(childFileInfo.FullName, childFileInfo.Name);
                //为了排序，添加一个列表。
                List<FindTaskListItem> tmpList = new List<FindTaskListItem>();

                var lineNum = 1;   //跳过第1行，这里就必须从1开始。
                for (int i = 1; i < lines.Length; i++)//第01行是文件状态，不属于正常的任务列表，忽略。
                {
                    lineNum++;

                    var lineText = lines[i];

                    if (string.IsNullOrEmpty(lineText)) continue;

                    var tmp = lineText.Replace(" ", "").Replace("\t", "").Replace("　", "").Replace("：", ":");
                    bool isTaskListItem = false;
                    Brush foreColor = null;
                    if (tmp.StartsWith("[-]"))
                    {
                        isTaskListItem = true;
                        foreColor = Brushes.Red;
                    }
                    else if (tmp.StartsWith("[%]"))
                    {
                        isTaskListItem = true;
                        foreColor = Brushes.Green;
                    }
                    else if (tmp.StartsWith("[+]"))
                    {
                        isTaskListItem = true;
                        foreColor = Brushes.Blue;
                    }
                    else if (tmp.StartsWith("[#]"))
                    {
                        isTaskListItem = true;
                        foreColor = Brushes.Brown;
                    }

                    if (isTaskListItem)
                    {
                        bool isSearchItem = false;
                        if (string.IsNullOrEmpty(searchHeader))
                        {
                            isSearchItem = true;
                        }
                        else if (tmp.StartsWith(searchHeader))
                        {
                            isSearchItem = true;
                        }
                        else
                        {
                            if (searchHeader == "![+]")
                            {
                                if (tmp.StartsWith("[-]") || tmp.StartsWith("[%]") || tmp.StartsWith("[#]"))
                                {
                                    isSearchItem = true;
                                }
                            }
                        }

                        if (isSearchItem)
                        {
                            int preIndex, nextIndex;
                            preIndex = lineText.IndexOf('[') + 1;
                            nextIndex = lineText.IndexOf(']') - 1;
                            TextDecorationCollection textDecoration = null;
                            if (tmp.StartsWith("[#]") || tmp.StartsWith("[+]"))
                            {
                                textDecoration = TextDecorations.Strikethrough;
                            }

                            FindTaskListItem newItem;
                            if (preIndex >= 0 && nextIndex >= 0 && nextIndex >= preIndex)
                            {
                                newItem = new FindTaskListItem(childFileInfo.FullName, childFileInfo.Name, lineNum, preIndex, nextIndex, lineText, foreColor,
                                     FindLineTreeViewItem.ItemType.TaskListItem, textDecoration);
                                tmpList.Add(newItem);
                            }
                            else
                            {
                                newItem = new FindTaskListItem(childFileInfo.FullName, childFileInfo.Name, lineNum, 0, nextIndex, lineText, foreColor,
                                     FindLineTreeViewItem.ItemType.TaskListItem, textDecoration);
                                tmpList.Add(newItem);
                            }

                            for (int j = i + 1; j < lines.Length; j++)
                            {
                                var line2Text = lines[j];
                                if (CustomMarkdownSupport.IsTaskLine(line2Text)) break;//到下一个任务列表项就停止。

                                if (CustomMarkdownSupport.IsDateLine(line2Text))
                                {
                                    var leftIndex = line2Text.IndexOf("[");
                                    var rightIndex = line2Text.IndexOf("]");
                                    if (leftIndex >= 0 && rightIndex > leftIndex)
                                    {
                                        var newDateItem = new FindTaskListTimeTagItem(childFileInfo.FullName, childFileInfo.Name,
                                            j + 1, leftIndex + 1, rightIndex - leftIndex - 1, line2Text, foreColor, FindLineTreeViewItem.ItemType.Normal);
                                        newItem.Items.Add(newDateItem);
                                    }
                                    else
                                    {
                                        var newDateItem = new FindTaskListTimeTagItem(childFileInfo.FullName, childFileInfo.Name,
                                            j + 1, 0, 0, line2Text, foreColor, FindLineTreeViewItem.ItemType.Normal);
                                        newItem.Items.Add(newDateItem);
                                    }
                                }
                            }

                            newItem.UpdateDateTime();
                            newItem.IsExpanded = true;
                        }
                    }
                }

                if (rbtnSortAsStartTime.IsChecked == true)
                {
                    tmpList.Sort(new FindTaskListItemTimeStartCompare());
                }
                else if (rbtnSortAsEndTime.IsChecked == true)
                {
                    tmpList.Sort(new FindTaskListItemTimeEndCompare());
                }

                foreach (var tmpItem in tmpList)
                {
                    fdi.Items.Add(tmpItem);
                }

                if (fdi.Items.Count > 0)
                {
                    treeView.Items.Add(fdi);
                    fdi.IsExpanded = true;
                }
            }

            //再递归处理子目录下的文件
            var childDirectories = directory.GetDirectories();
            foreach (var childDirectory in childDirectories)
            {
                FindTaskListInAllFiles(childDirectory.FullName, searchHeader, treeView);
            }
        }

        /// <summary>
        /// 按次序切换活动文档当前选中行中任务列表或时间标记的“任务完成状态”标记。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void miSwitchTaskListItemOrDateTimeLineState_Click(object sender, RoutedEventArgs e)
        {
            if (this.mainTabControl.SelectedItem == null) return;

            var editor = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (editor == null) return;

            editor.EditorBase.SwitchTaskListItemOrDateTimeLineState(false, false);
        }

        /// <summary>
        /// 在当前活动编辑器当前选中行前面添加“无序列表”标记。
        /// </summary>
        private void miSetAsListItem_Click(object sender, RoutedEventArgs e)
        {
            if (this.mainTabControl.SelectedItem == null) return;

            var editor = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (editor == null) return;

            editor.EditorBase.SwitchListMark(true);
        }

        /// <summary>
        /// 删除当前活动编辑器当前选中行前面的“无序列表”标记。
        /// </summary>
        private void miDeleteListItemMark_Click(object sender, RoutedEventArgs e)
        {
            var editor = this.ActivedEditor;
            if (editor == null) return;

            editor.EditorBase.SwitchListMark(false);
        }

        /// <summary>
        /// 将当前选定的“历史工作区目录”中记录的目录设置为当前工作区目录。
        /// </summary>
        private void miSetAsCurrentWorkspace_Click(object sender, RoutedEventArgs e)
        {
            if (lbxHistoryWorkspaces.SelectedItem == null)
            {
                LMessageBox.Show("　　请先选定一个历史工作区记录！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            var item = lbxHistoryWorkspaces.SelectedItem as RecentDirectoryListBoxItem;
            if (item == null) return;

            if (Globals.PathOfWorkspace.ToLower() == item.DirectoryPath.ToLower())
            {
                return;
            }

            ChangeWorkspace(item.DirectoryPath);
        }

        #region 最小化到托盘图标

        /// <summary>
        /// 用于点击托盘图标时切换窗口状态。
        /// </summary>
        WindowState windowState;

        /// <summary>
        /// 托盘图标对象。
        /// </summary>
        System.Windows.Forms.NotifyIcon notifyIcon;

        /// <summary>
        /// 创建 Windows 系统托盘图标。
        /// </summary>
        private void CreateNoticeIcon()
        {
            string iconPath;
            if (Globals.InstalledPath.EndsWith("\\"))
            {
                iconPath = Globals.InstalledPath + "App.ico";
            }
            else
            {
                iconPath = Globals.InstalledPath + "\\App.ico";
            }

            this.notifyIcon = new System.Windows.Forms.NotifyIcon();
            this.notifyIcon.BalloonTipText = Globals.AppName; //设置程序启动时显示的文本
            this.notifyIcon.Text = Globals.AppName;//最小化到托盘时，鼠标点击时显示的文本
            this.notifyIcon.Icon = new System.Drawing.Icon(iconPath);//程序图标
            this.notifyIcon.Visible = true;
            notifyIcon.MouseDoubleClick += OnNotifyIconDoubleClick;
            notifyIcon.MouseClick += NotifyIcon_MouseClick;
            //this.notifyIcon.ShowBalloonTip(1000);
        }

        /// <summary>
        /// 托盘图标的快捷菜单。
        /// </summary>
        private ContextMenu iconContextMenu = null;

        /// <summary>
        /// 点击托盘图标时弹出快捷菜单。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void NotifyIcon_MouseClick(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            if (iconContextMenu == null)
            {
                //ControlTemplate SubMenuItemControlTemplate = Globals.MainWindow.TryFindResource("SubMenuItemControlTemplate") as ControlTemplate;

                iconContextMenu = new ContextMenu()
                {
                    FontFamily = this.FontFamily,
                    FontSize = 14,
                    Style = TryFindResource("MetroContextMenu") as Style,
                };
                TextOptions.SetTextFormattingMode(iconContextMenu, TextFormattingMode.Display);
                MenuItem miShowWindow = new MenuItem()
                {
                    Header = "显示窗口(_S)",
                    Style = TryFindResource("MetroMenuItem") as Style,
                };
                miShowWindow.Click += MiShowWindow_Click;

                MenuItem miExit = new MenuItem()
                {
                    Header = "退出(_X)",
                    Style = TryFindResource("MetroMenuItem") as Style,
                };
                miExit.Click += MiExit_Click;

                this.iconContextMenu.Items.Add(miShowWindow);
                this.iconContextMenu.Items.Add(miExit);
            }

            iconContextMenu.IsOpen = true;
        }

        /// <summary>
        /// 点击托盘图标快捷菜单中的“退出程序”，关闭程序。
        /// </summary>
        private void MiExit_Click(object sender, RoutedEventArgs e)
        {
            //App.Current.Shutdown();//注意，这能使用这个
            this.isForceExit = true;//强行忽略这个字段的值。
            this.Close();
        }

        /// <summary>
        /// 点击托盘图标中的“显示窗口”，使程序主窗口显示出来。
        /// </summary>
        private void MiShowWindow_Click(object sender, RoutedEventArgs e)
        {
            this.Show();
            WindowState = windowState;
        }

        /// <summary>
        /// 双击托盘图标显示程序主窗口。
        /// </summary>
        private void OnNotifyIconDoubleClick(object sender, EventArgs e)
        {
            this.Show();
            WindowState = windowState;
        }

        private bool isCloseToIcon = false;
        /// <summary>
        /// 关闭到托盘图标。
        /// </summary>
        public bool IsCloseToIcon
        {
            get
            {
                return isCloseToIcon;
            }
        }

        private bool isPopupContextToolbarEnabled = true;
        /// <summary>
        /// 是否在编辑时自动弹出上下文快捷工具栏。
        /// </summary>
        public bool IsPopupContextToolbarEnabled
        {
            get { return isPopupContextToolbarEnabled; }
            set
            {
                this.isPopupContextToolbarEnabled = value;
                RefreshIsPopupContextToolbarEnabled(value);
            }
        }

        private void RefreshIsPopupContextToolbarEnabled(bool value)
        {
            miIsPopupContextToolbarEnabled.IsChecked = value;
            if (value)
            {
                btnSwitchPopupToolbarEnabled.ToolTip = "已开启快捷工具栏";
                backgroundGridIsPopupToolbarEnabled.Background = Brushes.DarkCyan;
            }
            else
            {
                btnSwitchPopupToolbarEnabled.ToolTip = "已关闭快捷工具栏";
                backgroundGridIsPopupToolbarEnabled.Background = Brushes.Transparent;
            }
        }

        /// <summary>
        /// 是否在编译工作区时忽略被加密的文件，否则每次都会要求输入密码。
        /// 这个选项默认打开，因为加密文件生成CHM工程文件时无法读取名称。
        /// </summary>
        public bool IgnoreEncryptedFiles { get; private set; }

        /// <summary>
        /// 是否在编译工作区时忽略被标记为“废弃”的文件。此特性默认打开。
        /// </summary>
        public bool IgnoreAbortedFiles { get; private set; } = true;

        /// <summary>
        /// 忽略isCloseToIcon的值，强制退出。
        /// </summary>
        private bool isForceExit = false;

        /// <summary>
        /// 开关“CloseToIcon”属性。此属性决定点击窗口右上角“关闭”按钮时是退出程序还是隐藏到托盘图标。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void miCloseToIcon_Click(object sender, RoutedEventArgs e)
        {
            isCloseToIcon = miCloseToIcon.IsChecked = !miCloseToIcon.IsChecked;

            App.ConfigManager.Set("CloseToIcon", isCloseToIcon.ToString());
        }
        #endregion

        /// <summary>
        /// 在工作区管理器中选定的目录位置查找该目录下所有 Markdown 文件中定义的锚。
        /// </summary>
        private void miSearchAnchorsInDirectory_Click(object sender, RoutedEventArgs e)
        {
            var wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;

            if (wtvi == null)
            {
                LMessageBox.Show("　　请先在工作区管理器中选择一个目录作为查找目标！", Globals.AppName,
                     MessageBoxButton.OK, MessageBoxImage.Information);
                return;
            }

            if (wtvi.IsDirectoryExists == false)
            {
                LMessageBox.Show("　　请选择目录而不是其它项目！", Globals.AppName,
                     MessageBoxButton.OK, MessageBoxImage.Information);
                return;
            }

            if (wtvi.FullPath.EndsWith("~") || wtvi.FullPath.EndsWith("~\\"))
            {
                LMessageBox.Show("　　以波型符结尾的目录是资源目录，不允许执行此操作！", Globals.AppName,
                     MessageBoxButton.OK, MessageBoxImage.Information);
                return;
            }

            //锚的定义方式：
            //[锚名](@锚ID)
            //锚名可以省略，但锚ID不能省略。
            //编译后，会变成这样：
            //<a id="锚ID">锚名</a>
            FindTextInAllFiles(wtvi.FullPath, @"\[.*\]\(@.*\)", true, tvFindAndReplace);

            if (tcRightToolBar.SelectedItem != tiFindResult)
            {
                tcRightToolBar.SelectedItem = tiFindResult;
            }

            if (cdRightToolsArea.ActualWidth < 100)
            {
                cdMainEditArea.Width =
                    cdRightToolsArea.Width = new GridLength(3, GridUnitType.Star);
            }

        }

        /// <summary>
        /// 根据工作区管理器中指定的目录，查找该目录下所有 Markdown 文件中定义的所有指定类型任务列表项。
        /// </summary>
        /// <param name="headerKey">要查找什么类型的任务列表，需要传入“[-]”、“[+]”、“![+]”、“[#]”、“”、“[%]”这几种之一。</param>
        /// <param name="treeView">指定要将查找结果显示在哪个树型框中。</param>
        private void FindTaskListInDirectory(string headerKey, TreeView treeView)
        {
            if (treeView == null) treeView = tvTaskList;

            treeView.Items.Clear();

            var wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;

            if (wtvi == null)
            {
                LMessageBox.Show("　　请先在工作区管理器中选择一个目录作为查找目标！", Globals.AppName,
                     MessageBoxButton.OK, MessageBoxImage.Information);
                return;
            }

            if (wtvi.IsDirectoryExists == false)
            {
                LMessageBox.Show("　　请选择目录而不是其它项目！", Globals.AppName,
                     MessageBoxButton.OK, MessageBoxImage.Information);
                return;
            }

            if (wtvi.FullPath.EndsWith("~") || wtvi.FullPath.EndsWith("~\\"))
            {
                LMessageBox.Show("　　以波型符结尾的目录是资源目录，不允许执行此操作！", Globals.AppName,
                     MessageBoxButton.OK, MessageBoxImage.Information);
                return;
            }

            FindTaskListInAllFiles(wtvi.FullPath, headerKey, treeView);

            if (tvTaskList.Items.Count <= 0)
            {
                tvTaskList.Items.Add("<没找到结果...>");
            }

            if (tcRightToolBar.SelectedItem != tiTaskList)
            {
                tcRightToolBar.SelectedItem = tiTaskList;
            }

            if (cdRightToolsArea.ActualWidth < 100)
            {
                cdMainEditArea.Width =
                    cdRightToolsArea.Width = new GridLength(3, GridUnitType.Star);
            }
        }

        /// <summary>
        /// 根据工作区管理器中指定的目录，查找该目录下所有 Markdown 文件中定义的所有“未开始”的任务列表项。
        /// </summary>
        private void miSearchUnStartTaskListItemInDirectory_Click(object sender, RoutedEventArgs e)
        {
            FindTaskListInDirectory("[-]", tvTaskList);
        }

        /// <summary>
        /// 根据工作区管理器中指定的目录，查找该目录下所有 Markdown 文件中定义的所有“未完成”的任务列表项。
        /// </summary>
        private void miSearchUnFinishedTaskListItemInDirectory_Click(object sender, RoutedEventArgs e)
        {
            FindTaskListInDirectory("![+]", tvTaskList);
        }

        /// <summary>
        /// 根据工作区管理器中指定的目录，查找该目录下所有 Markdown 文件中定义的所有“正在进行”的任务列表项。
        /// </summary>
        private void miSearchProcessingTaskListItemInDirectory_Click(object sender, RoutedEventArgs e)
        {
            FindTaskListInDirectory("[%]", tvTaskList);
        }

        /// <summary>
        /// 根据工作区管理器中指定的目录，查找该目录下所有 Markdown 文件中定义的所有“废弃”的任务列表项。
        /// </summary>
        private void miSearchAbortedTaskListItemInDirectory_Click(object sender, RoutedEventArgs e)
        {
            FindTaskListInDirectory("[#]", tvTaskList);
        }

        /// <summary>
        /// 根据工作区管理器中指定的目录，查找该目录下所有 Markdown 文件中定义的所有“已完成”的任务列表项。
        /// </summary>
        private void miSearchFinishedTaskListItemInDirectory_Click(object sender, RoutedEventArgs e)
        {
            FindTaskListInDirectory("[+]", tvTaskList);
        }

        /// <summary>
        /// 根据工作区管理器中指定的目录，查找该目录下所有 Markdown 文件中定义的所有任务列表项。
        /// </summary>
        private void miSearchAllTaskListItemInDirectory_Click(object sender, RoutedEventArgs e)
        {
            FindTaskListInDirectory("", tvTaskList);
        }

        /// <summary>
        /// 根据主界面“查找范围”框的限制，查找所有“未开始”的任务列表项。
        /// </summary>
        private void miSearchUnStartTaskListItem_Click(object sender, RoutedEventArgs e)
        {
            FindTaskList("[-]", tvTaskList);
        }

        /// <summary>
        /// 根据主界面“查找范围”框的限制，查找所有“正在进行”的任务列表项。
        /// </summary>
        private void miSearchProcessingTaskListItem_Click(object sender, RoutedEventArgs e)
        {
            FindTaskList("[%]", tvTaskList);
        }

        /// <summary>
        /// 根据主界面“查找范围”框的限制，查找所有“废弃”的任务列表项。
        /// </summary>
        private void miSearchAbortedTaskListItem_Click(object sender, RoutedEventArgs e)
        {
            FindTaskList("[#]", tvTaskList);
        }

        /// <summary>
        /// 根据主界面“查找范围”框的限制，查找所有“已完成”的任务列表项。
        /// </summary>
        private void miSearchFinishedTaskListItem_Click(object sender, RoutedEventArgs e)
        {
            FindTaskList("[+]", tvTaskList);
        }

        /// <summary>
        /// 根据主界面“查找范围”框的限制，查找所有任务列表项。
        /// </summary>
        private void miSearchAllTaskListItem_Click(object sender, RoutedEventArgs e)
        {
            FindTaskList("", tvTaskList);
        }

        /// <summary>
        /// 根据主界面“查找范围”框的限制，查找所有任务列表项。
        /// </summary>
        private void btnFindTaskListItem_Click(object sender, RoutedEventArgs e)
        {
            FindTaskList("", tvTaskList);
        }

        /// <summary>
        /// 根据主界面“查找范围”框的限制，查找所有“未完成”的任务列表项。
        /// </summary>
        private void miSearchUnFinishedTaskListItem_Click(object sender, RoutedEventArgs e)
        {
            FindTaskList("![+]", tvTaskList);
        }

        /// <summary>
        /// 打开“查找/替换”面板，准备在当前文档中查找当前活动编辑器中选定的文本。
        /// 打开面板后立即查找第一个符合条件的文本片段。
        /// 如果当前没有打开任何文档，就准备在全工作区中查找。
        /// </summary>
        private void miFindInActiveDocument_Click(object sender, RoutedEventArgs e)
        {
            if (rdFindAndReplace.ActualHeight <= 40)
            {
                rdFindAndReplace.Height = new GridLength(140, GridUnitType.Auto);
                cmbFindText.UpdateLayout();
            }

            var editor = ActivedEditor;
            if (editor != null)
            {
                cmbFindText.Text = editor.EditorBase.SelectedText;
            }
            else
            {
                cmbSearchArea.SelectedIndex = 2;//没有打开任何文档，查找全工作区。
                return;
            }

            cmbSearchArea.SelectedIndex = 0;

            cmbFindText.Focus();
            if (cmbFindText.Text.Length > 0)
            {
                btnFind_Click(sender, e);
            }

            //独立窗口效果不好，整合到主界面上。
            //var activeEditor = this.mainTabControl.SelectedItem as MarkdownEditor;
            //if (activeEditor != null)
            //{
            //    FindReplaceDialog.ShowForFind(activeEditor.EditorBase, this);
            //}
        }

        /// <summary>
        /// 打开“查找/替换”面板，准备在当前文档中查找并替换文本。
        /// 如果当前没有打开任何文档，就准备在全工作区中查找并替换。
        /// </summary>
        private void miFindAndReplaceInActiveDocument_Click(object sender, RoutedEventArgs e)
        {
            //打开查找替换面板
            rdFindAndReplace.Height = new GridLength(0, GridUnitType.Auto);
            cmbFindText.UpdateLayout();

            var editor = ActivedEditor;
            if (editor != null)
            {
                cmbFindText.Text = editor.EditorBase.SelectedText;
            }
            else
            {
                cmbSearchArea.SelectedIndex = 2;//没有打开任何文档，查找全工作区。
                return;
            }

            cmbSearchArea.SelectedIndex = 0;

            if (cmbFindText.Text.Length > 0)
            {
                cmbReplaceTextInputBox.Focus();
            }

            //var activeEditor = this.mainTabControl.SelectedItem as MarkdownEditor;
            //if (activeEditor != null)
            //{
            //    FindReplaceDialog.ShowForReplace(activeEditor.EditorBase, this);
            //}
        }

        /// <summary>
        /// 在当前活动编辑器的当前行插入一个时间标签文本（形如： [2016-05-13 10:00] xxx)。
        /// 如果当前行本身就是时间标签，则将其时间更改为当前时间。
        /// </summary>
        private void miInsertDate_Click(object sender, RoutedEventArgs e)
        {
            InsertDateText();
        }

        /// <summary>
        /// 从指定的时间生成格式化的日期文本。格式化后的时间类似：2016-05-13。
        /// 这样做的目的是保证每个时间标签的长度一致。
        /// </summary>
        /// <param name="datetime"></param>
        /// <returns></returns>
        public static string FormatDateText(DateTime datetime)
        {
            var srcText = datetime.Date.ToShortDateString();
            var spans = srcText.Split(new char[] { '-', '.', ',', '，', '。', '－', '、', '．', '/' }, StringSplitOptions.RemoveEmptyEntries);

            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < spans.Length; i++)
            {
                var span = spans[i];
                if (span.Length == 1)
                {
                    span = '0' + span;
                }
                sb.Append(span + "-");
            }

            var result = sb.ToString();
            if (result.EndsWith("-")) return result.Substring(0, result.Length - 1);

            return result;
        }

        /// <summary>
        /// 从指定的时间生成格式化的日期、时间文本。格式化后的时间类似：2016-05-13 10:01。
        /// 这样做的目的是保证每个时间标签的长度一致。
        /// </summary>
        public static string FormatDateTimeText(DateTime datetime)
        {
            var srcText = datetime.Date.ToShortDateString();
            var spans = srcText.Split(new char[] { '-', '.', ',', '，', '。', '－', '、', '．', '/' }, StringSplitOptions.RemoveEmptyEntries);

            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < spans.Length; i++)
            {
                var span = spans[i];
                if (span.Length == 1)
                {
                    span = '0' + span;
                }
                sb.Append(span + "-");
            }

            var result = sb.ToString();
            if (result.EndsWith("-")) result = result.Substring(0, result.Length - 1);

            var timeText = datetime.ToShortTimeString();
            StringBuilder sb2 = new StringBuilder();
            var spans2 = timeText.Split(new char[] { ':' });
            for (int i = 0; i < spans2.Length; i++)
            {
                var span = spans2[i];
                if (span.Length == 1)
                {
                    span = '0' + span;
                }
                sb2.Append(span + ":");
            }
            var result2 = sb2.ToString();
            result += " " + result2.Substring(0, result2.Length - 1);

            return result;
        }

        /// <summary>
        /// 在当前活动编辑器的当前行插入一个时间标签文本（形如： [2016-05-13 10:00] xxx)。
        /// 如果当前行本身就是时间标签，则将其时间更改为当前时间。
        /// </summary>
        private void InsertDateText()
        {
            var activeEditor = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (activeEditor == null) return;

            var now = DateTime.Now;
            var header = "";
            if (activeEditor.EditorBase.Document.GetLineByOffset(activeEditor.EditorBase.SelectionStart).Offset == activeEditor.EditorBase.SelectionStart)
            {
                header = " ";//自动格式化一下
            }

            var line = activeEditor.EditorBase.Document.GetLineByOffset(activeEditor.EditorBase.SelectionStart);
            var lineText = activeEditor.EditorBase.Document.GetText(line.Offset, line.Length);
            if (CustomMarkdownSupport.IsTaskLine(lineText))
            {
                //在最后插入一个新行（2016年5月26日废弃此方案）
                #region 废弃，这个方案不好——如果任务列表中项目过多，可能导致难以查看
                //DocumentLine lastDateLine = null;
                //for (int i = line.LineNumber + 1; i <= activeEditor.EditorBase.Document.LineCount; i++)//LineNumber从１开始。
                //{
                //    var l = activeEditor.EditorBase.Document.GetLineByNumber(i);
                //    var lt = activeEditor.EditorBase.Document.GetText(l.Offset, l.Length);

                //    if (CustomMarkdownSupport.IsDateLine(lt))
                //    {
                //        lastDateLine = l;
                //    }
                //    else if (CustomMarkdownSupport.IsTaskLine(lt) || lt.StartsWith("#"))
                //    {
                //        if (i - 2 > line.LineNumber)
                //        {
                //            lastDateLine = activeEditor.EditorBase.Document.GetLineByNumber(i - 2);
                //        }
                //        else if (i - 1 > line.LineNumber)
                //        {
                //            lastDateLine = activeEditor.EditorBase.Document.GetLineByNumber(i - 2);
                //        }
                //        break;
                //    }
                //}

                //if (lastDateLine == null) lastDateLine = line;//如果找不到，就直接在当前行后面添加。 
                #endregion

                var markText = CustomMarkdownSupport.GetHeaderOfTaskListItem(lineText);
                if (markText.Contains("-")) markText = "[-]";
                else if (markText.Contains("%")) markText = "[%]";
                else if (markText.Contains("#")) markText = "[#]";
                else if (markText.Contains("+")) markText = "[+]";
                else markText = "";

                header = " ";

                var newDateLine = $"{header}[{FormatDateText(now)} {now.ToShortTimeString()}]{markText} ";
                activeEditor.EditorBase.Document.Insert(/*lastDateLine*/line.EndOffset, $"\r\n\r\n{newDateLine}");
                var destSel = /*lastDateLine*/line.EndOffset + 4 + newDateLine.Length;
                if (destSel < activeEditor.EditorBase.Document.TextLength)
                {
                    activeEditor.EditorBase.Select(destSel, 0);
                    activeEditor.EditorBase.ScrollToLine(/*lastDateLine*/line.LineNumber + 2);
                }
            }
            else if (CustomMarkdownSupport.IsDateLine(lineText))
            {
                //修改当前行的时间
                var indexStart = lineText.IndexOf("[");
                var indexEnd = lineText.IndexOf("]");
                if (indexStart >= 0 && indexEnd >= 0)
                {
                    //修改当前时间
                    var newLineText = lineText.Substring(0, indexStart + 1) + FormatDateText(DateTime.Now) +
                       $" {now.ToShortTimeString()}" + lineText.Substring(indexEnd);
                    activeEditor.EditorBase.Document.Replace(line.Offset, line.Length, newLineText);
                }
                else
                {
                    //在当前位置插入时间
                    activeEditor.EditorBase.SelectedText = $"{header}[{FormatDateText(now)} {now.ToShortTimeString()}] ";
                    var destSel = activeEditor.EditorBase.SelectionStart + activeEditor.EditorBase.SelectionLength;
                    if (destSel < activeEditor.EditorBase.Document.TextLength)
                    {
                        activeEditor.EditorBase.Select(destSel, 0);
                    }
                }
            }
            else
            {
                //在当前位置插入时间

                activeEditor.EditorBase.SelectedText = $"{header}[{FormatDateText(now)} {now.ToShortTimeString()}] ";
                var destSel = activeEditor.EditorBase.SelectionStart + activeEditor.EditorBase.SelectionLength;
                if (destSel < activeEditor.EditorBase.Document.TextLength)
                {
                    activeEditor.EditorBase.Select(destSel, 0);
                }
            }
        }

        /// <summary>
        /// 开关“IgnoreEncryptedFile”选项。此选项决定在编译工作区时是否跳过被加密的文件。
        /// 如此选项被关闭，则编译被加密的每个文件时都有可能要求用户输入一次密码。
        /// </summary>
        private void miIgnoreEncryptedFile_Click(object sender, RoutedEventArgs e)
        {
            this.IgnoreEncryptedFiles =
            miIgnoreEncryptedFile.IsChecked = !miIgnoreEncryptedFile.IsChecked;
            App.WorkspaceConfigManager.Set("IgnoreEncryptedFiles", miIgnoreEncryptedFile.IsChecked.ToString());
        }

        /// <summary>
        /// 开关“IgnoreAbortedFile”选项。此选项决定在编译工作区时是否跳过被标记为“废弃”的文件。
        /// （即第一行为“[#] xxx”的Markdown文件。
        /// </summary>
        private void miIgnoreAbortedFile_Click(object sender, RoutedEventArgs e)
        {
            this.IgnoreAbortedFiles =
            miIgnoreAbortedFile.IsChecked = !miIgnoreAbortedFile.IsChecked;
            App.WorkspaceConfigManager.Set("IgnoreAbortedFile", miIgnoreAbortedFile.IsChecked.ToString());
        }

        /// <summary>
        /// 开关“HideExamAnswer”选项。此选项用于在编译 Html 时决定编译后的页面中试题的答案是否直接显示。
        /// 若此选项开启，则不显示答案；若关闭此选项，则编译后的 Html 中直接显示答案。
        /// </summary>
        private void miHideExamAnswer_Click(object sender, RoutedEventArgs e)
        {
            this.hideExamAnswer =
            miHideExamAnswer.IsChecked = !miHideExamAnswer.IsChecked;
            App.WorkspaceConfigManager.Set("HideExamAnswer", miHideExamAnswer.IsChecked.ToString());
        }

        /// <summary>
        /// 关闭“查找/替换”面板。
        /// </summary>
        private void btnCloseFindAndReplacePanel_Click(object sender, RoutedEventArgs e)
        {
            rdFindAndReplace.Height = new GridLength(0);
            if (ActivedEditor != null) FocusActiveEditor();
        }

        /// <summary>
        /// 激活当前活动编辑器（使之获得焦点）。
        /// </summary>
        public void FocusActiveEditor()
        {
            //重置焦点。
            this.mainTabControl.Focus();

            var efi = ActivedEditor;
            if (efi == null) return;

            efi.EditorBase.UpdateLayout();
            var destSel = efi.EditorBase.Text.Length;
            if (destSel < efi.EditorBase.Document.TextLength)
            {
                efi.EditorBase.Select(destSel, 0);
            }
            efi.UpdateLayout();
            efi.EditorBase.Focus();
        }

        /// <summary>
        /// 在当前活动编辑器中查找下一个符合条件的文本片段。
        /// </summary>
        private void btnFind_Click(object sender, RoutedEventArgs e)
        {
            Find();
        }

        /// <summary>
        /// 在当前活动编辑器中查找下一个符合条件的文本片段。
        /// </summary>
        public void Find()
        {
            if (!FindNext(cmbFindText.Text))
                SystemSounds.Beep.Play();

            cmbFindText.Items.Insert(0, cmbFindText.Text);
        }

        /// <summary>
        /// 根据“查找/替换”面板中配置的选项，生成正则表达式对象。
        /// </summary>
        /// <param name="textToFind">要查找的文本。</param>
        /// <param name="leftToRight">是否顺序查找（从前向后、从后向前）</param>
        /// <param name="forceRegex">是否强制按正则表达式查找。</param>
        /// <returns>返回一个正则对象。</returns>
        private Regex GetRegEx(string textToFind, bool leftToRight, bool forceRegex)
        {
            RegexOptions options = RegexOptions.None;
            if (cbSearchUp.IsChecked == true && !leftToRight)
                options |= RegexOptions.RightToLeft;
            if (cbCaseSensitive.IsChecked == false)
                options |= RegexOptions.IgnoreCase;

            options |= RegexOptions.Multiline;

            if (cbRegex.IsChecked == true || forceRegex)
            {
                return new Regex(textToFind, options);
            }
            else
            {
                string pattern = Regex.Escape(textToFind);
                if (cbWildcards.IsChecked == true)
                {
                    pattern = pattern.Replace("\\*", ".*").Replace("\\?", ".");
                }

                Regex convert = new Regex(@"\\.");
                pattern = convert.Replace(pattern, new MatchEvaluator(ConvertEscapeChars));

                if (cbWholeWord.IsChecked == true)
                    pattern = "\\b" + pattern + "\\b";
                return new Regex(pattern, options);
            }
        }

        /// <summary>
        /// 在当前活动编辑器中查找下一个符合条件的文本片段。
        /// </summary>
        private bool FindNext(string textToFind)
        {
            try
            {
                var activeEditor = ActivedEditor;
                if (activeEditor == null) return false;

                Regex regex = GetRegEx(textToFind, false, false);
                int start = regex.Options.HasFlag(RegexOptions.RightToLeft) ?
                activeEditor.EditorBase.SelectionStart : activeEditor.EditorBase.SelectionStart + activeEditor.EditorBase.SelectionLength;
                Match match = regex.Match(activeEditor.EditorBase.Text, start);

                if (!match.Success)  // start again from beginning or end
                {
                    if (regex.Options.HasFlag(RegexOptions.RightToLeft))
                        match = regex.Match(activeEditor.EditorBase.Text, activeEditor.EditorBase.Text.Length);
                    else
                        match = regex.Match(activeEditor.EditorBase.Text, 0);
                }

                if (match.Success)
                {
                    var destSel = match.Index;
                    if (destSel < activeEditor.EditorBase.Document.TextLength)
                    {
                        activeEditor.EditorBase.Select(destSel, match.Length);
                    }
                    TextLocation loc = activeEditor.EditorBase.Document.GetLocation(match.Index);
                    activeEditor.EditorBase.ScrollTo(loc.Line, loc.Column);
                }

                return match.Success;
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return false;
            }
        }

        /// <summary>
        /// 在当前活动编辑器中按指定条件顺序替换。
        /// </summary>
        private void btnReplace_Click(object sender, RoutedEventArgs e)
        {
            var activeEditor = ActivedEditor;
            if (activeEditor == null) return;

            try
            {
                Regex regex = GetRegEx(cmbFindText.Text, false, false);
                string input = activeEditor.EditorBase.Text.Substring(activeEditor.EditorBase.SelectionStart, activeEditor.EditorBase.SelectionLength);
                Match match = regex.Match(input);
                bool replaced = false;
                if (match.Success && match.Index == 0 && match.Length == input.Length)
                {
                    var newTextForReplace = cmbReplaceTextInputBox.Text;

                    Regex convert1 = new Regex(@"\\.");
                    newTextForReplace = convert1.Replace(newTextForReplace, new MatchEvaluator(ConvertEscapeChars));

                    Regex convert2 = new Regex(@"\\{1,2}x");
                    var matches = convert2.Matches(newTextForReplace);
                    for (int j = matches.Count - 1; j >= 0; j--)
                    {
                        var xMatch = matches[j];
                        if (xMatch.Value == @"\\x")
                        {
                            newTextForReplace = newTextForReplace.Substring(0, xMatch.Index) + @"\x" +
                                newTextForReplace.Substring(xMatch.Index + xMatch.Length);
                        }
                        else if (xMatch.Value == @"\x")
                        {
                            newTextForReplace = newTextForReplace.Substring(0, xMatch.Index) + match.Value   //match.Value，引用查找结果
                                + newTextForReplace.Substring(xMatch.Index + xMatch.Length);
                        }
                    }

                    activeEditor.EditorBase.Document.Replace(activeEditor.EditorBase.SelectionStart,
                    activeEditor.EditorBase.SelectionLength, newTextForReplace);
                    replaced = true;
                }

                if (!FindNext(cmbFindText.Text) && !replaced)
                    SystemSounds.Beep.Play();

                cmbFindText.Items.Insert(0, cmbFindText.Text);
                cmbReplaceTextInputBox.Items.Insert(0, cmbReplaceTextInputBox.Text);
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }


        /// <summary>
        /// 先取剪贴板中的文本，再针对这些文本执行替换操作，最后粘贴替换过的文本。
        /// </summary>
        private void btnReplaceAndPaste_Click(object sender, RoutedEventArgs e)
        {
            var activeEditor = ActivedEditor;
            if (activeEditor == null) return;

            var sourceText = Clipboard.GetText(TextDataFormat.UnicodeText);
            if (string.IsNullOrWhiteSpace(sourceText))
            {
                LMessageBox.Show("剪贴板中没有可供替换并粘贴的文本。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            try
            {
                Regex regex = GetRegEx(cmbFindText.Text, true, false);

                var matchs = regex.Matches(sourceText);
                int replacedCount = 0;
                for (int i = matchs.Count - 1; i >= 0; i--)
                {
                    var match = matchs[i];
                    var newTextForReplace = cmbReplaceTextInputBox.Text;

                    Regex convert = new Regex(@"\\.");
                    newTextForReplace = convert.Replace(newTextForReplace, new MatchEvaluator(ConvertEscapeChars));

                    Regex convert2 = new Regex(@"\\{1,2}x");
                    var matches = convert2.Matches(newTextForReplace);
                    for (int j = matches.Count - 1; j >= 0; j--)
                    {
                        var xMatch = matches[j];
                        if (xMatch.Value == @"\\x")
                        {
                            newTextForReplace = newTextForReplace.Substring(0, xMatch.Index) + @"\x" +
                                newTextForReplace.Substring(xMatch.Index + xMatch.Length);
                        }
                        else if (xMatch.Value == @"\x")
                        {
                            newTextForReplace = newTextForReplace.Substring(0, xMatch.Index) + match.Value   //match.Value，引用查找结果
                                + newTextForReplace.Substring(xMatch.Index + xMatch.Length);
                        }
                    }

                    sourceText = sourceText.Substring(0, match.Index) + newTextForReplace + sourceText.Substring(match.Index + match.Length);
                    replacedCount++;
                }

                activeEditor.EditorBase.BeginChange();
                activeEditor.EditorBase.Document.Replace(activeEditor.EditorBase.SelectionStart, activeEditor.EditorBase.SelectionLength, sourceText);
                activeEditor.EditorBase.EndChange();

                var findText = cmbFindText.Text;
                for (int i = cmbFindText.Items.Count - 1; i >= 0; i--)
                {
                    if (cmbFindText.Items[i] as string == findText)
                    {
                        cmbFindText.Items.RemoveAt(i);
                    }
                }
                cmbFindText.Items.Insert(0, findText);

                var replaceText = cmbReplaceTextInputBox.Text;
                for (int i = cmbReplaceTextInputBox.Items.Count - 1; i >= 0; i--)
                {
                    if (cmbReplaceTextInputBox.Items[i] as string == replaceText)
                    {
                        cmbReplaceTextInputBox.Items.RemoveAt(i);
                    }
                }
                cmbReplaceTextInputBox.Items.Insert(0, replaceText);
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        /// <summary>
        /// 在当前文档中按“查找”框中指定的关键词查找所有符合条件的结果，并全部替换为“替换”框中指定的文本。
        /// </summary>
        private void btnReplaceAll_Click(object sender, RoutedEventArgs e)
        {
            var activeEditor = ActivedEditor;
            if (activeEditor == null) return;

            var result = LMessageBox.Show("你确定把所有找到的 “" +
                                cmbFindText.Text + "” 替换成 “" + cmbReplaceTextInputBox.Text + "”吗？",
                                "Lunar Markdown Edit - 全部替换", MessageBoxButton.OKCancel, MessageBoxImage.Question);
            if (result == MessageBoxResult.OK)
            {
                try
                {
                    Regex regex = GetRegEx(cmbFindText.Text, true, false);
                    activeEditor.EditorBase.BeginChange();

                    var matchs = regex.Matches(activeEditor.EditorBase.Text);
                    int replacedCount = 0;
                    for (int i = matchs.Count - 1; i >= 0; i--)
                    {
                        var match = matchs[i];

                        if (cbReplaceOnlyInSelectionText.IsChecked == true)
                        {
                            if (match.Index + match.Length > activeEditor.EditorBase.SelectionStart + activeEditor.EditorBase.SelectionLength) continue;
                            if (match.Index < activeEditor.EditorBase.SelectionStart) break;//不需要再考虑index更小的情况了
                        }

                        var newTextForReplace = cmbReplaceTextInputBox.Text;

                        Regex convert = new Regex(@"\\.");
                        newTextForReplace = convert.Replace(newTextForReplace, new MatchEvaluator(ConvertEscapeChars));

                        Regex convert2 = new Regex(@"\\{1,2}x");
                        var matches = convert2.Matches(newTextForReplace);
                        for (int j = matches.Count - 1; j >= 0; j--)
                        {
                            var xMatch = matches[j];
                            if (xMatch.Value == @"\\x")
                            {
                                newTextForReplace = newTextForReplace.Substring(0, xMatch.Index) + @"\x" +
                                    newTextForReplace.Substring(xMatch.Index + xMatch.Length);
                            }
                            else if (xMatch.Value == @"\x")
                            {
                                newTextForReplace = newTextForReplace.Substring(0, xMatch.Index) + match.Value   //match.Value，引用查找结果
                                    + newTextForReplace.Substring(xMatch.Index + xMatch.Length);
                            }
                        }

                        activeEditor.EditorBase.Document.Replace(match.Index, match.Length, newTextForReplace);
                        replacedCount++;
                    }
                    activeEditor.EditorBase.EndChange();

                    var findText = cmbFindText.Text;
                    for (int i = cmbFindText.Items.Count - 1; i >= 0; i--)
                    {
                        if (cmbFindText.Items[i] as string == findText)
                        {
                            cmbFindText.Items.RemoveAt(i);
                        }
                    }
                    cmbFindText.Items.Insert(0, findText);

                    var replaceText = cmbReplaceTextInputBox.Text;
                    for (int i = cmbReplaceTextInputBox.Items.Count - 1; i >= 0; i--)
                    {
                        if (cmbReplaceTextInputBox.Items[i] as string == replaceText)
                        {
                            cmbReplaceTextInputBox.Items.RemoveAt(i);
                        }
                    }
                    cmbReplaceTextInputBox.Items.Insert(0, replaceText);

                    if (cbReplaceOnlyInSelectionText.IsChecked == true)
                    {
                        LMessageBox.Show($"在当前文档的选定文本中替换了【{replacedCount}】处文本。", Globals.AppName,
                              MessageBoxButton.OK, MessageBoxImage.Information);
                    }
                    else
                    {
                        LMessageBox.Show($"在当前文档中替换了【{replacedCount}】处文本。", Globals.AppName,
                              MessageBoxButton.OK, MessageBoxImage.Information);
                    }
                }
                catch (Exception ex)
                {
                    LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                }
            }
        }

        /// <summary>
        /// 替换框中“\r”会被当作回车符、“\n”会被当作换行符、“\t”会被解释为Tab符、“\\”会被解释为单个“\”。
        /// 所以想要替换“\r”本身时，需要转义。
        /// </summary>
        /// <param name="m">正则表达式匹配项。</param>
        /// <returns>转义后的文本。</returns>
        static string ConvertEscapeChars(Match m)
        {
            string x = m.ToString();
            switch (m.Value)
            {
                case "\\\\": return "\\";
                case "\\r": return "\r";
                case "\\n": return "\n";
                case "\\t": return "\t";
                default: return m.Value;
            }
        }

        /// <summary>
        /// 刷新“查找/替换”面板上各部件的“可用/禁用”状态。避免用户误操作。
        /// </summary>
        private void cmbSearchArea_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            RefreshFindButtonsStatus();
        }

        /// <summary>
        /// 刷新“查找/替换”面板上各部件的“可用/禁用”状态。避免用户误操作。
        /// </summary>
        private void RefreshFindButtonsStatus()
        {
            if (btnFind == null) return;
            if (btnReplace == null) return;
            if (btnReplaceAll == null) return;
            if (cmbReplaceTextInputBox == null) return;
            if (cbCaseSensitive == null) return;
            if (cbWholeWord == null) return;
            if (cbRegex == null) return;
            if (cbWildcards == null) return;
            if (cbSearchUp == null) return;
            if (cbReplaceOnlyInSelectionText == null) return;

            switch (cmbSearchArea.SelectedIndex)
            {
                case 0:
                    {
                        btnFind.IsEnabled =
                            btnReplace.IsEnabled =
                            btnReplaceAll.IsEnabled =
                            cmbReplaceTextInputBox.IsEnabled =
                            cbSearchUp.IsEnabled =
                            cbReplaceOnlyInSelectionText.IsEnabled = true;

                        cbSearchUp.Foreground = Brushes.Black;
                        cbReplaceOnlyInSelectionText.Foreground = Brushes.Brown;

                        //cbCaseSensitive.IsEnabled =
                        //cbRegex.IsEnabled =
                        //cbSearchUp.IsEnabled =
                        //cbWholeWord.IsEnabled =
                        //cbWildcards.IsEnabled = true;
                        break;
                    }
                default:
                    {
                        btnFind.IsEnabled =
                            btnReplace.IsEnabled =
                            btnReplaceAll.IsEnabled =
                            cmbReplaceTextInputBox.IsEnabled =
                            cbSearchUp.IsEnabled =
                            cbReplaceOnlyInSelectionText.IsEnabled = false;

                        cbReplaceOnlyInSelectionText.IsChecked = false;

                        cbSearchUp.Foreground = Brushes.Gray;
                        cbReplaceOnlyInSelectionText.Foreground = Brushes.Gray;

                        //cbCaseSensitive.IsEnabled =
                        //cbRegex.IsEnabled =
                        //cbSearchUp.IsEnabled =
                        //cbWholeWord.IsEnabled =
                        //cbWildcards.IsEnabled = false;
                        break;
                    }
            }
        }

        /// <summary>
        /// 主界面“查找任务列表”按钮右侧边附加的小三角按钮，点击会弹出一个快捷菜单，可以用来查找指定类型的任务列表。
        /// 这两个按钮看起来就是个 SplitButton。此方法用以实现类似 SplitButton 的效果。
        /// </summary>
        private void btnFindTaskListItems_AppendArrow_Initialized(object sender, EventArgs e)
        {
            //SplitterButton的弹出菜单要处理一下，左键单显示，右键不显示
            splitMenu.Placement = PlacementMode.Absolute;
            splitMenu.PlacementTarget = btnFindTaskListItems_AppendArrow;

            btnFindTaskListItems_AppendArrow.ContextMenu = null;

            splitMenu.Opened += SplitMenu_Opened;
        }

        private void SplitMenu_Opened(object sender, RoutedEventArgs e)
        {
            var point = btnFindTaskListItems_AppendArrow.PointToScreen(new Point(0, 0));
            splitMenu.HorizontalOffset = point.X - splitMenu.ActualWidth + btnFindTaskListItems_AppendArrow.ActualWidth + 6;
            splitMenu.VerticalOffset = point.Y + btnFindTaskListItems_AppendArrow.ActualHeight - 1;
        }

        /// <summary>
        /// 主界面“查找标题”按钮右侧边附加的小三角按钮，点击会弹出一个快捷菜单，可以用来查找指定级别的标题。
        /// 这两个按钮看起来就是个 SplitButton。此方法用以实现类似 SplitButton 的效果。
        /// </summary>
        private void btnFindHeaders_AppendArrow_Initialized(object sender, EventArgs e)
        {
            //SplitterButton的弹出菜单要处理一下，左键单显示，右键不显示
            splitMenu2.PlacementTarget = btnFindHeaders_AppendArrow;
            splitMenu2.Placement = PlacementMode.Absolute;

            btnFindHeaders_AppendArrow.ContextMenu = null;

            splitMenu2.Opened += SplitMenu2_Opened;
        }

        private void SplitMenu2_Opened(object sender, RoutedEventArgs e)
        {
            var point = btnFindHeaders_AppendArrow.PointToScreen(new Point(0, 0));
            splitMenu2.HorizontalOffset = point.X - splitMenu2.ActualWidth + btnFindHeaders_AppendArrow.ActualWidth + 6;
            splitMenu2.VerticalOffset = point.Y + btnFindHeaders_AppendArrow.ActualHeight - 1;
        }

        /// <summary>
        /// 主界面“查找任务列表”按钮右侧边附加的小三角按钮，点击会弹出一个快捷菜单，可以用来查找指定类型的任务列表。
        /// 这两个按钮看起来就是个 SplitButton。
        /// </summary>
        private void btnFindTaskListItems_AppendArrow_Click(object sender, RoutedEventArgs e)
        {
            splitMenu.IsOpen = true;
        }

        /// <summary>
        /// 主界面“查找标题”按钮右侧边附加的小三角按钮，点击会弹出一个快捷菜单，可以用来查找指定级别的标题。
        /// 这两个按钮看起来就是个 SplitButton。
        /// </summary>
        private void btnFindHeaders_AppendArrow_Click(object sender, RoutedEventArgs e)
        {
            splitMenu2.IsOpen = true;
        }

        /// <summary>
        /// 用“region { } region”块包围当前选定的文本行。
        /// </summary>
        private void miWrapWithRegionMark_Click(object sender, RoutedEventArgs e)
        {
            WrapWithRegionMark();
        }

        internal void WrapWithRegionMark()
        {
            var editor = ActivedEditor;

            if (editor.EditorBase.SelectionLength == 0)
            {
                editor.EditorBase.SelectedText = "\r\n{ \r\n\r\n\r\n}\r\n";
                var destSel = editor.EditorBase.SelectionStart + 4;
                if (destSel < editor.EditorBase.Document.TextLength)
                {
                    editor.EditorBase.Select(destSel, 0);
                }
                return;
            }

            var fstLine = editor.EditorBase.Document.GetLineByOffset(editor.EditorBase.SelectionStart);
            var lastLine = editor.EditorBase.Document.GetLineByOffset(editor.EditorBase.SelectionStart + editor.EditorBase.SelectionLength);

            editor.EditorBase.Document.Insert(lastLine.EndOffset, "\r\n}\r\n");
            editor.EditorBase.Document.Insert(fstLine.Offset, "\r\n{ \r\n");
            var destSel2 = fstLine.Offset + 4;
            if (destSel2 < editor.EditorBase.Document.TextLength)
            {
                editor.EditorBase.Select(destSel2, 0);
            }
        }

        /// <summary>
        /// 打印预览当前活动编辑器。
        /// </summary>
        private void miPrintPreview_Click(object sender, RoutedEventArgs e)
        {
            PrintPreview();
        }

        /// <summary>
        /// 打印当前活动编辑器。
        /// </summary>
        private void miPrintDocument_Click(object sender, RoutedEventArgs e)
        {
            PrintDocument();
        }

        /// <summary>
        /// 对当前活动编辑器执行“打印预览”。
        /// </summary>
        public void PrintPreview()
        {
            var editor = ActivedEditor;
            if (editor == null) return;

            if (editor.EditorBase.PageSetupDialog())              // .NET dialog
            {
                editor.EditorBase.PrintPreviewDialog(editor.ShortFileName);           // WPF print preview dialog
            }
        }

        /// <summary>
        /// 打印当前活动编辑器。
        /// </summary>
        public void PrintDocument()
        {
            var editor = ActivedEditor;
            if (editor == null) return;

            if (editor.EditorBase.PageSetupDialog())              // .NET dialog
            {
                editor.EditorBase.PrintDialog(editor.ShortFileName);           // WPF print preview dialog
            }

            //调用方法示例，勿删。
            //editor.EditorBase.PrintPreviewDialog(filename);   // WPF print preview dialog, filename as document title
            //editor.EditorBase.PrintDialog();                   // WPF print dialog
            //editor.EditorBase.PrintDialog(filename);          // WPF print dialog, filename as document title
            //editor.EditorBase.PrintDirect();                   // prints to default or previously selected printer
            //editor.EditorBase.PrintDirect(filename)           // prints to default or previously selected printer, filename as document title
        }

        /// <summary>
        /// 查找功能在启用“正则表达式”时，禁止使用“通配符”。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void cbRegex_Checked(object sender, RoutedEventArgs e)
        {
            cbWildcards.IsChecked = false;
        }

        /// <summary>
        /// 查找功能在启用“通配符”时，禁止使用“正则表达式”。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void cbWildcards_Checked(object sender, RoutedEventArgs e)
        {
            cbRegex.IsChecked = false;
        }

        /// <summary>
        /// 开关“AutoNumberHeaders”。开启后编译的 Html 文档中所有“标题<h1></h1>...<h6></h6>”都会自动添加序号。
        /// </summary>
        private void miAutoNumberHeaders_Click(object sender, RoutedEventArgs e)
        {
            miAutoNumberHeaders.IsChecked = !miAutoNumberHeaders.IsChecked;
            this.AutoNumberHeaders = miAutoNumberHeaders.IsChecked;
            App.WorkspaceConfigManager.Set("AutoNumberHeaders", miAutoNumberHeaders.IsChecked.ToString());
        }

        /// <summary>
        /// 根据主界面“查找范围”框中定义的范围，查找所有六个级别的标题。
        /// </summary>
        private void btnFindHeaders_Click(object sender, RoutedEventArgs e)
        {
            string keyWord = @"^ {0,3}#{1,6}.*$";
            FindHeaders(keyWord, tvFindAndReplace);
        }

        /// <summary>
        /// 根据主界面“查找范围”框中定义的范围，查找所有六个级别的标题。须指定用于查找标题的正则表达式。
        /// </summary>
        /// <param name="keyWord">要用于查找标题的正则表达式。</param>
        /// <param name="treeView">指定要将查找结果输出到哪个树型框。</param>
        private void FindHeaders(string keyWord, TreeView treeView)
        {
            FindText(keyWord, (cmbSearchArea.SelectedItem as ComboBoxItem).Tag.ToString(), true, treeView);

            if (treeView.HasItems == false)
            {
                treeView.Items.Add(new TextBlock() { Text = "<...未找到结果...>", });
            }

            //防止右工具栏被折叠
            if (treeView == tvFindAndReplace)
            {
                if (tcRightToolBar.SelectedItem != tiFindResult)
                {
                    tcRightToolBar.SelectedItem = tiFindResult;
                }

                if (cdRightToolsArea.ActualWidth < 140)
                {
                    cdRightToolsArea.Width = new GridLength(2, GridUnitType.Star);
                }
            }
        }

        /// <summary>
        /// 根据主界面“查找范围”框中定义的范围，查找所有以“#”开头的标题。
        /// </summary>
        private void miSearchH1_Click(object sender, RoutedEventArgs e)
        {
            FindHeaders(@"^ {0,3}#[^#].*$", tvFindAndReplace);
        }

        /// <summary>
        /// 根据主界面“查找范围”框中定义的范围，查找所有以“##”开头的标题。
        /// </summary>
        private void miSearchH2_Click(object sender, RoutedEventArgs e)
        {
            FindHeaders(@"^ {0,3}##[^#].*$", tvFindAndReplace);
        }

        /// <summary>
        /// 根据主界面“查找范围”框中定义的范围，查找所有以“###”开头的标题。
        /// </summary>
        private void miSearchH3_Click(object sender, RoutedEventArgs e)
        {
            FindHeaders(@"^ {0,3}###[^#].*$", tvFindAndReplace);
        }

        /// <summary>
        /// 根据主界面“查找范围”框中定义的范围，查找所有以“####”开头的标题。
        /// </summary>
        private void miSearchH4_Click(object sender, RoutedEventArgs e)
        {
            FindHeaders(@"^ {0,3}####[^#].*$", tvFindAndReplace);
        }

        /// <summary>
        /// 根据主界面“查找范围”框中定义的范围，查找所有以“#####”开头的标题。
        /// </summary>
        private void miSearchH5_Click(object sender, RoutedEventArgs e)
        {
            FindHeaders(@"^ {0,3}#####[^#].*$", tvFindAndReplace);
        }

        /// <summary>
        /// 根据主界面“查找范围”框中定义的范围，查找所有以“######”开头的标题。
        /// </summary>
        private void miSearchH6_Click(object sender, RoutedEventArgs e)
        {
            FindHeaders(@"^ {0,3}######.*$", tvFindAndReplace);
        }

        /// <summary>
        /// 根据主界面“查找范围”框中定义的范围，查找所有以“#”开头的标题。
        /// </summary>
        private void miSearchAllH_Click(object sender, RoutedEventArgs e)
        {
            FindHeaders(@"^ {0,3}#{1,6}.*$", tvFindAndReplace);
        }

        /// <summary>
        /// 打开 LME 教程（这是个 chm 文档）。
        /// </summary>
        private void btnBook_Click(object sender, RoutedEventArgs e)
        {
            var bookFullName = Globals.InstalledPath + "Lunar_Markdown_Editor_教程.chm";
            if (File.Exists(bookFullName))
            {
                System.Diagnostics.Process.Start(bookFullName);
            }
        }

        private List<EnToChEntry> dictionary = new List<EnToChEntry>();
        /// <summary>
        /// 英译中词典，用于智能提示功能。
        /// </summary>
        public List<EnToChEntry> Dictionary
        {
            get { return dictionary; }
        }

        /// <summary>
        /// 向英译中词典中添加词条。
        /// </summary>
        private void miAddEnToChEntry_Click(object sender, RoutedEventArgs e)
        {
            var window = new AddEnToChEntryWindow()
            {
                WindowStartupLocation = WindowStartupLocation.CenterOwner,
                Owner = this,
            };
            window.EntryAdded += Window_EntryAdded;
            window.Show();
        }

        /// <summary>
        /// 自动完成提示窗口中添加词条时触发此方法。
        /// </summary>
        private void Window_EntryAdded(object sender, EntryAddedEventArgs e)
        {
            dictionary.Add(e.Entry);
            foreach (var i in this.mainTabControl.Items)
            {
                var edit = i as MarkdownEditor;
                if (edit == null) continue;

                edit.EditorBase.AddEntryToCompletionWindow(e.Entry);
            }
        }

        /// <summary>
        /// 编辑英译中词典中的“用户词典”。
        /// </summary>
        private void miEditEnToChUserDictonary_Click(object sender, RoutedEventArgs e)
        {
            var result = EditDictionary(Globals.PathOfUserDictionary, Globals.AppName + " - 编辑用户词典");
            if (result != string.Empty)
            {
                LMessageBox.Show("不能编辑用户词典。错误消息如下：\r\n" + result,
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
            else
            {
                LoadDictionary();
            }
        }

        /// <summary>
        /// 编辑英译中词典中的“工作区词典”。
        /// </summary>
        private void miEditEnToChWorkspaceDictionary_Click(object sender, RoutedEventArgs e)
        {
            var result = EditDictionary(Globals.PathOfWorkspaceDictionary, Globals.AppName + " - 编辑工作区词典");
            if (result != string.Empty)
            {
                LMessageBox.Show("不能编辑工作区词典。错误消息如下：\r\n" + result,
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
            else
            {
                LoadDictionary();
            }
        }

        /// <summary>
        /// 编辑英译中词典文件。
        /// </summary>
        /// <param name="filePath">要编辑的词典文件的完整路径。</param>
        /// <param name="editorWindowTitle">编辑器窗口标题文本。</param>
        /// <returns>返回 string.Empty 表示一切正常完成。</returns>
        private string EditDictionary(string filePath, string editorWindowTitle = null)
        {
            try
            {
                if (File.Exists(filePath) == false)
                {
                    File.WriteAllText(filePath, "; 英文\t中文\t字母缩写\t注释\r\n", Encoding.UTF8);
                }

                if (File.Exists(filePath) == false)
                {
                    return "未找到文件且无法创建。";
                }

                //编辑词典文件
                var dictionaryEditor = new DictionaryEditor(filePath)
                {
                    Owner = this,
                    WindowStartupLocation = WindowStartupLocation.CenterOwner,
                };

                if (string.IsNullOrWhiteSpace(editorWindowTitle))
                {
                    dictionaryEditor.Title = Globals.AppName;
                }
                else { dictionaryEditor.Title = editorWindowTitle; }

                dictionaryEditor.ShowDialog();

                #region 旧版，以纯文本形式编辑，不够直观。
                //var pw = new PlainTextEditor(filePath)
                //{
                //    Owner = this,
                //    WindowStartupLocation = WindowStartupLocation.CenterOwner,
                //    Title = Globals.AppName + " - 编辑词典",
                //};

                //using (StreamReader sr = new StreamReader(filePath))
                //{
                //    var content = sr.ReadToEnd();
                //    string fstLine = "";
                //    int index = content.IndexOf("\r\n");

                //    if (index >= 0)
                //    {
                //        fstLine = content.Substring(0, index - 2);
                //    }

                //    if (fstLine.StartsWith(";格式说明：") == false)
                //    {
                //        content = ";格式说明： 英文[Tab]中文[Tab]字母缩写[Tab]注释\r\n" + content;
                //    }

                //    pw.tbx.Text = content;
                //}

                //pw.ShowDialog(); 
                #endregion
                return string.Empty;
            }
            catch (Exception ex)
            {
                return ex.Message + "\r\n" + ex.StackTrace;
            }
        }

        /// <summary>
        /// 开关“自动提示译词”功能。
        /// </summary>
        private void miIsEnToChineseDictEnabled_Click(object sender, RoutedEventArgs e)
        {
            if (this.IsAutoCompletionEnabled == false)
            {
                var result = LMessageBox.Show("需要打开【自动完成】，要继续吗？", Globals.AppName,
                     MessageBoxButton.YesNo, MessageBoxImage.Question);
                if (result != MessageBoxResult.Yes) return;

                miIsAutoCompletionEnabled.IsChecked = true;
                this.IsAutoCompletionEnabled = true;
                App.ConfigManager.Set("IsAutoCompletionEnabled", true.ToString());
            }

            miIsEnToChineseDictEnabled.IsChecked = !miIsEnToChineseDictEnabled.IsChecked;
            this.IsEnToChineseDictEnabled = miIsEnToChineseDictEnabled.IsChecked;
            App.ConfigManager.Set("IsEnToChineseDictEnabled", miIsEnToChineseDictEnabled.IsChecked.ToString());
        }

        /// <summary>
        /// 整理活动编辑器中的空段落，将过多的空段合并。
        /// </summary>
        private void miFormatParagraphs_Click(object sender, RoutedEventArgs e)
        {
            var edit = this.ActivedEditor;
            if (edit == null) return;

            cbRegex.IsChecked = true;
            cmbFindText.Text = @"((\r)|(\n)|(\r\n)){2,}";
            cmbReplaceTextInputBox.Text = @"\r\n";
            cmbSearchArea.SelectedIndex = 0;

            if (edit.EditorBase.SelectionLength == 0)
            {
                //当前未选中文本，在整个文档中替换。
                btnReplaceAll_Click(sender, e);
            }
            else
            {
                //当前选中了文本，只在选定的各行中替换
                bool shouldRestore = false;
                if (cbReplaceOnlyInSelectionText.IsChecked != true)
                {
                    cbReplaceOnlyInSelectionText.IsChecked = true;
                    shouldRestore = true;
                }

                btnReplaceAll_Click(sender, e);

                if (shouldRestore)
                {
                    cbReplaceOnlyInSelectionText.IsChecked = false;
                }
            }
        }

        /// <summary>
        /// 折叠左工具栏。
        /// </summary>
        private void btnCollapseLeftToolArea_Click(object sender, RoutedEventArgs e)
        {
            SwitchLeftToolBarToggle();
        }

        /// <summary>
        /// 折叠右工具栏。
        /// </summary>
        private void btnCollapseRightToolArea_Click(object sender, RoutedEventArgs e)
        {
            SwitchRightToolBarToggle();
        }

        /// <summary>
        /// 折叠“查找/替换”面板。
        /// </summary>
        private void btnCollapseFindAndReplacePanel_Click(object sender, RoutedEventArgs e)
        {
            SwitchFindAndReplacePanelToggle();
        }

        /// <summary>
        /// 切换 AppendTimeOfCompiling 属性的值。该属性用于决定是否在编译的 Html 文档末尾添加“编译时间文本”。
        /// </summary>
        private void miAppendTimeOfCompiling_Click(object sender, RoutedEventArgs e)
        {
            this.AppendTimeOfCompiling =
                        miAppendTimeOfCompiling.IsChecked = !miAppendTimeOfCompiling.IsChecked;
            App.WorkspaceConfigManager.Set("AppendTimeOfCompiling", miAppendTimeOfCompiling.IsChecked.ToString());
        }

        /// <summary>
        /// 是否在编译Html时将Markdown文件中单独占一行的图像文件链接的标题编译到图像顶部。默认情况下是编译到底部的。
        /// </summary>
        private void miTableAndImageCaptionAtTop_Click(object sender, RoutedEventArgs e)
        {
            this.ImageTitleAtTop =
                        miImageTitleAtTop.IsChecked = !miImageTitleAtTop.IsChecked;
            App.WorkspaceConfigManager.Set("ImageTitleAtTop", miImageTitleAtTop.IsChecked.ToString());
        }

        /// <summary>
        /// 是否在编译Html时将二维文字表的标题编译到底部。默认情况下是编译到顶部的。
        /// </summary>
        private void miTableCaptionAtBottom_Click(object sender, RoutedEventArgs e)
        {
            this.TableCaptionAtBottom =
                        miTableCaptionAtBottom.IsChecked = !miTableCaptionAtBottom.IsChecked;
            App.WorkspaceConfigManager.Set("TableCaptionAtBottom", miTableCaptionAtBottom.IsChecked.ToString());
        }

        /// <summary>
        /// 切换 FormatAfterCompile 属性的值。该属性用于决定是否对编译好的 Html 文件进行格式化。
        /// **对 Html 的格式化是用 NSoup 实现的。
        /// </summary>
        private void miFormatAfterCompile_Click(object sender, RoutedEventArgs e)
        {
            this.FormatAfterCompile =
            miFormatAfterCompile.IsChecked = !miFormatAfterCompile.IsChecked;
            App.WorkspaceConfigManager.Set("FormatAfterCompile", miFormatAfterCompile.IsChecked.ToString());
        }

        /// <summary>
        /// 查找所有 TODO Comment，查找结果显示在右工具栏“查找结果”页面。
        /// </summary>
        private void miSearchAllTodoComment_Click(object sender, RoutedEventArgs e)
        {
            string mark = (sender as MenuItem).Tag.ToString();
            tvTaskList.Items.Clear();
            FindTodoComment((cmbSearchArea.SelectedItem as ComboBoxItem).Tag.ToString(), tvTaskList, mark);

            if (tvTaskList.Items.Count <= 0)
            {
                tvTaskList.Items.Add(new TreeViewItem() { Header = $"<没找到 {mark} 注释...>", });
            }

            //防止右工具栏被隐藏。
            if (tcRightToolBar.SelectedItem != tiTaskList)
            {
                tcRightToolBar.SelectedItem = tiTaskList;
            }

            if (cdRightToolsArea.ActualWidth < 100)
            {
                cdMainEditArea.Width =
                    cdRightToolsArea.Width = new GridLength(3, GridUnitType.Star);
            }
        }

        /// <summary>
        /// 根据指定的查找范围来查找所有 TODO Comment，含（TODO/DONE等）。
        /// </summary>
        /// <param name="searchArea">查找范围：包括“ActiveDocument”、“OpenedDocuments”、“AllFiles”。</param>
        /// <param name="treeView">指定要将查找到的结果放在哪个 TreeView 中。</param>
        /// <param name="mark">传入“TODO”（或“DONE”、“DOING”等）标记文本。</param>
        private void FindTodoComment(string searchArea, TreeView treeView, string mark)
        {
            if (treeView == null) treeView = tvTaskList;

            treeView.Items.Clear();
            switch (searchArea)
            {
                case "ActiveDocument":
                    {
                        FindTodoCommentsInActiveDocuments(treeView, mark);
                        break;
                    }
                case "OpenedDocuments":
                    {
                        FindTodoCommentsInOpenedDocuments(treeView, mark);
                        break;
                    }
                case "AllFiles":
                    {
                        //这个需要用到递归
                        FindTodoCommentInAllFiles(Globals.PathOfWorkspace, treeView, mark);
                        break;
                    }
            }
        }

        /// <summary>
        /// 在所有打开的文档中查找 TODO Comments。
        /// </summary>
        /// <param name="treeView">指定要将查找到的结果放在哪个 TreeView 中。</param>
        /// <param name="mark">传入TODO标记。（“TODO”或“DONE”等）。</param>
        private void FindTodoCommentsInOpenedDocuments(TreeView treeView, string mark = "TODO")
        {
            foreach (var item in this.mainTabControl.Items)
            {
                var efi = item as MarkdownEditor;
                if (efi != null)
                {
                    Brush markBrush;
                    switch (mark)
                    {
                        case "DONE": { markBrush = Brushes.Blue; break; }
                        case "DOING": { markBrush = Brushes.Green; break; }
                        default: { markBrush = Brushes.Red; break; }
                    }

                    FindDocumentTreeViewItem fdi = new FindDocumentTreeViewItem(efi.FullFilePath, efi.ShortFileName);
                    var lines = efi.EditorBase.Document.Lines;
                    for (int i = 0; i < lines.Count; i++)
                    {
                        var line = lines[i];
                        var lineText = efi.EditorBase.Document.GetText(line.Offset, line.Length);
                        if (string.IsNullOrEmpty(lineText)) continue;

                        string todoCommentMark;
                        string todoCommentTail;
                        if (CustomMarkdownSupport.IsTodoCommentLine(lineText, out todoCommentMark, out todoCommentTail, mark))
                        {
                            FindLineTreeViewItem newItem = new FindLineTreeViewItem(efi.FullFilePath, efi.ShortFileName, line.LineNumber,
                                todoCommentMark.Length, lineText.Length - todoCommentMark.Length, lineText, markBrush, FindLineTreeViewItem.ItemType.TodoComment);
                            fdi.Items.Add(newItem);
                        }
                    }

                    if (fdi.Items.Count > 0)
                    {
                        treeView.Items.Add(fdi);
                        fdi.IsExpanded = true;
                        (fdi.Items[0] as TreeViewItem).IsSelected = true;
                    }
                }
            }
        }

        /// <summary>
        /// 在活动文档中查找所有 TODO Comments。
        /// </summary>
        /// <param name="treeView">指定要将查找到的结果放在哪个 TreeView 中。</param>
        /// <param name="mark">传入TODO标记。（“TODO”或“DONE”等）。</param>
        private void FindTodoCommentsInActiveDocuments(TreeView treeView, string mark = "TODO")
        {
            var efi = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (efi != null)
            {
                Brush markBrush;
                switch (mark)
                {
                    case "DONE": { markBrush = Brushes.Blue; break; }
                    case "DOING": { markBrush = Brushes.Green; break; }
                    default: { markBrush = Brushes.Red; break; }
                }

                FindDocumentTreeViewItem fdi = new FindDocumentTreeViewItem(efi.FullFilePath, efi.ShortFileName);
                var lines = efi.EditorBase.Document.Lines;
                for (int i = 0; i < lines.Count; i++)
                {
                    var line = lines[i];
                    var lineText = efi.EditorBase.Document.GetText(line.Offset, line.Length);
                    if (string.IsNullOrEmpty(lineText)) continue;

                    string todoCommentMark;
                    string todoCommentTail;
                    if (CustomMarkdownSupport.IsTodoCommentLine(lineText, out todoCommentMark, out todoCommentTail, mark))
                    {
                        FindLineTreeViewItem newItem = new FindLineTreeViewItem(efi.FullFilePath, efi.ShortFileName, line.LineNumber,
                            todoCommentMark.Length, lineText.Length - todoCommentMark.Length, lineText, markBrush, FindLineTreeViewItem.ItemType.TodoComment);
                        fdi.Items.Add(newItem);
                    }
                }

                if (fdi.Items.Count > 0)
                {
                    treeView.Items.Add(fdi);
                    fdi.IsExpanded = true;
                    (fdi.Items[0] as TreeViewItem).IsSelected = true;
                }
            }
        }

        /// <summary>
        /// 从指定目录开始查找能找到的所有 Markdown 文件（含子目录中的文件）中的 TODO Comment。
        /// </summary>
        /// <param name="directoryPath">开始查找的目录全路径。</param>
        /// <param name="treeView">指定将查找结果输出到哪个树型框。</param>
        /// <param name="mark">传入TODO标记。（“TODO”或“DONE”等）。</param>
        private void FindTodoCommentInAllFiles(string directoryPath, TreeView treeView, string mark = "TODO")
        {
            if (Directory.Exists(directoryPath) == false) return;
            var directory = new DirectoryInfo(directoryPath);

            //先处理当前目录下的文件
            var childFilesInfos = directory.GetFiles();

            foreach (var childFileInfo in childFilesInfos)
            {
                if (childFileInfo.FullName.ToLower().EndsWith(".md") == false) continue;

                string[] lines;

                //已打开的文件，按打开的情况算，没打开的，按磁盘文本查找。
                var fileEditor = GetOpenedEditor(childFileInfo.FullName);
                if (fileEditor != null)
                {
                    lines = fileEditor.EditorBase.Text.Replace("\r", "").Split(new char[] { '\n' });
                }
                else
                {
                    lines = File.ReadAllLines(childFileInfo.FullName);
                }

                FindDocumentTreeViewItem fdi = new FindDocumentTreeViewItem(childFileInfo.FullName, childFileInfo.Name);

                var lineNum = 0;
                for (int i = 0; i < lines.Length; i++)
                {
                    lineNum++;

                    var lineText = lines[i];
                    if (string.IsNullOrEmpty(lineText)) continue;

                    string todoCommentMark;
                    string todoCommentTail;
                    if (CustomMarkdownSupport.IsTodoCommentLine(lineText, out todoCommentMark, out todoCommentTail, mark))
                    {
                        Brush markBrush;
                        switch (mark)
                        {
                            case "DONE": { markBrush = Brushes.Blue; break; }
                            case "DOING": { markBrush = Brushes.Green; break; }
                            default: { markBrush = Brushes.Red; break; }
                        }

                        FindLineTreeViewItem newItem = new FindLineTreeViewItem(childFileInfo.FullName,
                            childFileInfo.Name, lineNum, todoCommentMark.Length, lineText.Length - todoCommentMark.Length,
                            lineText, markBrush, FindLineTreeViewItem.ItemType.TodoComment);
                        fdi.Items.Add(newItem);
                    }
                }

                if (fdi.Items.Count > 0)
                {
                    treeView.Items.Add(fdi);
                    fdi.IsExpanded = true;
                }
            }

            //再递归处理子目录下的文件
            var childDirectories = directory.GetDirectories();
            foreach (var childDirectory in childDirectories)
            {
                FindTodoCommentInAllFiles(childDirectory.FullName, treeView, mark);
            }
        }

        /// <summary>
        /// 折叠左工具栏底部的资源预览区域。
        /// </summary>
        private void btnResetLeftToolbarLayout_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (PerspectiveMode == Perspective.EditingAndPresentation)
            {
                //把演讲模式下的行为和其它透视图下的行为区分开来。
                rdLeftToolsTop.Height =
                    rdResourcePreviewArea.Height = new GridLength(1, GridUnitType.Star);
            }
            else
            {
                //在没有想到更好的办法之前，还是简单点儿（折叠/展开）好。
                if (rdResourcePreviewArea.ActualHeight <= 0)
                {
                    rdLeftToolsTop.Height =
                        rdResourcePreviewArea.Height = new GridLength(1, GridUnitType.Star);
                }
                else
                {
                    rdLeftToolsTop.Height = new GridLength(1, GridUnitType.Star);
                    rdResourcePreviewArea.Height = new GridLength(0);
                }

                //三态切换，行为有些诡异，还是先废除的好
                //在不同的状态间循环切换。
                //if (rdResourcePreviewArea.ActualHeight <= 0)
                //{
                //    rdLeftToolsTop.Height =
                //        rdResourcePreviewArea.Height = new GridLength(1, GridUnitType.Star);
                //}
                //else if (rdResourcePreviewArea.Height.GridUnitType != GridUnitType.Star)//拖动调整过
                //{
                //    rdLeftToolsTop.Height = new GridLength(0);
                //    rdResourcePreviewArea.Height = new GridLength(1, GridUnitType.Star);
                //}
                //else
                //{
                //    rdLeftToolsTop.Height = new GridLength(1, GridUnitType.Star);
                //    rdResourcePreviewArea.Height = new GridLength(0);
                //}
            }
        }

        /// <summary>
        /// 手工指定（设置）用户自行下载、安装的微软公司 Html Help Workshop 应用程序的磁盘位置。
        /// </summary>
        private void miSetHtmlHelpWorkshopFullName_Click(object sender, RoutedEventArgs e)
        {
            SetHtmlHelpWorkshopFullName();
        }

        /// <summary>
        /// 在工作区中当前选定的目录的父目录中创建 Markdown 文件。
        /// 目录较长时，再回去找父目录会比较烦人。
        /// </summary>
        private void miNewSameLevelFile_Click(object sender, RoutedEventArgs e)
        {
            var selItem = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (selItem == null)
            {
                LMessageBox.Show("请先在工作区选择一个条目作为创建文件的位置。", Globals.AppName,
                     MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            if (selItem.IsResourceDirectory || selItem.IsResourceFile)
            {
                LMessageBox.Show("资源文件夹或资源文件附近不能创建 Markdown 文件。", Globals.AppName,
                     MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            if (selItem.IsDirectoryExists)
            {
                var parentItem = selItem.Parent as WorkspaceTreeViewItem;
                if (parentItem == null)
                {
                    LMessageBox.Show("找不到当前位置的上级，无法确定同级文档应该创建在什么位置。", Globals.AppName,
                         MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }

                parentItem.IsSelected = true;
                var index = parentItem.Items.IndexOf(selItem);
                NewFile(false, false, parentItem, index + 1);
            }
            else if (selItem.IsMarkdownFilePath)
            {
                NewFile(false);
            }
        }

        /// <summary>
        /// 调用 Windows 系统中安装的 Lunar Concept 绘制概念图。
        /// </summary>
        private void miInsertConcept_Click(object sender, RoutedEventArgs e)
        {
            //尝试从注册表中读取安装的hhw.exe的路径
            RegistryKey regSubKey;
            RegistryKey regKey = Registry.LocalMachine;
            string strRegPath = @"SOFTWARE\Lunar Concept";
            regSubKey = regKey.OpenSubKey(strRegPath);
            if (regSubKey == null)
            {
                LMessageBox.Show("未能在此计算机上找到 Lunar Concept，无法调用它来绘制概念图。", Globals.AppName,
                     MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            string lunarConceptInstalledPath = regSubKey.GetValue("InstalledPath").ToString();

            if (File.Exists(lunarConceptInstalledPath) == false)
            {
                LMessageBox.Show("此计算机上未安装 Lunar Concept，无法调用它来绘制概念图。", Globals.AppName,
                     MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            string imageResourceFolderPath;
            var selItem = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (selItem == null)
            {
                LMessageBox.Show("请在左侧工作区管理器中选择一个“Images~”资源文件夹作为添加概念图文件的位置。", Globals.AppName,
                         MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            WorkspaceTreeViewItem imageResourceItem = null;

            if (selItem.IsMarkdownFilePath)
            {
                var filePath = selItem.FullPath;
                imageResourceFolderPath = selItem.FullPath.Substring(0, selItem.FullPath.Length - 3) + "~\\Images~\\";
            }
            else if (selItem.IsImageResourceDirectory)
            {
                imageResourceFolderPath = selItem.FullPath;
                imageResourceItem = selItem;
            }
            else if (selItem.IsDirectoryExists)
            {
                var directoryInfo = new DirectoryInfo(selItem.FullPath);
                var metaFileImageResourceDirectoryPath =
                    (directoryInfo.FullName.EndsWith("\\") ? directoryInfo.FullName : (directoryInfo.FullName + "\\")) +
                    $"_{directoryInfo.Name}~\\Images~\\";
                imageResourceFolderPath = metaFileImageResourceDirectoryPath;
                foreach (var item in selItem.Items)
                {
                    var wtvisub = item as WorkspaceTreeViewItem;
                    if (wtvisub == null) continue;
                    if (wtvisub.FullPath == metaFileImageResourceDirectoryPath)
                    {
                        imageResourceItem = wtvisub;
                        break;
                    }
                }
            }
            else
            {
                LMessageBox.Show("请在左侧工作区管理器中选择一个“Images~”资源文件夹作为添加概念图文件的位置。", Globals.AppName,
                           MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            try
            {
                var shortCommentName = InputBox.Show(Globals.AppName, "请输入概念图短名（不能以“_”开头，尽量以字母开头）：", "概念图", true, "不需要后缀名。", false);

                var shortName = ChinesePinYin.ToChinesePinYinText(shortCommentName);

                if (string.IsNullOrWhiteSpace(shortName) || shortName.StartsWith(".")) return;

                if (shortName.ToLower().EndsWith(".png") == false)
                {
                    shortName += ".png";
                }

                //刷新工作区管理器
                if (Directory.Exists(imageResourceFolderPath) == false)
                {
                    Directory.CreateDirectory(imageResourceFolderPath);

                    //须解决效率问题，不能直接调用此方法，也不能直接载入Xml。
                    //WorkspaceManager.RefreshWorkspaceTreeView(selItem.FullPath);

                    //基本思路：先添加“Images~”资源文件夹节点，再添加资源节点。
                    imageResourceItem = new WorkspaceTreeViewItem(
                        (Globals.MainWindow.ShowTitleInWorkspaceManager ? "图像~" : "Images~"),
                        imageResourceFolderPath, "[-]", "", "图像资源文件夹",
                        WorkspaceTreeViewItem.Type.ImageFolder, Globals.MainWindow);
                    selItem.Items.Add(imageResourceItem);
                }
                else
                {
                    imageResourceItem = FindWorkspaceTreeViewItem(imageResourceFolderPath);
                }

                var destFileName = imageResourceFolderPath + shortName;

                if (File.Exists(destFileName))
                {
                    LMessageBox.Show("　　已存在同名文件，操作无法完成。", Globals.AppName,
                               MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }

                File.Copy(Globals.InstalledPath + "ConceptExample.png", destFileName);

                if (imageResourceItem != null)
                {
                    var newImageItem = new WorkspaceTreeViewItem(destFileName, Globals.MainWindow);
                    imageResourceItem.Items.Add(newImageItem);
                    newImageItem.IsSelected = true;
                }

                var destFileInfo = new FileInfo(destFileName);
                var oldExtension = destFileInfo.Extension;

                var commentTextFilePath = destFileInfo.Directory.FullName + "\\" + "~.txt";

                if (shortCommentName.ToLower().EndsWith(".png") == false)
                {
                    shortCommentName += ".png";
                }

                if (File.Exists(commentTextFilePath))
                {
                    using (var stream = File.AppendText(commentTextFilePath))
                    {
                        stream.WriteLine($"{shortName}|{shortCommentName}");
                    }
                }
                else
                {
                    using (var stream = File.CreateText(commentTextFilePath))
                    {
                        stream.WriteLine($"{shortName}|{shortCommentName}");
                    }
                }

                System.Diagnostics.Process.Start(lunarConceptInstalledPath, $"\"{destFileName}\"");
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        /// <summary>
        /// 编辑由 Lunar Concept 生成的 png 格式的概念图。
        /// Lunar Concept 生成的 png 图形末尾被附加了一个纯文本的 XML 文件的所有内容，所以是可以编辑的。
        /// </summary>
        private void miEditConcept_Click(object sender, RoutedEventArgs e)
        {
            var selItem = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (selItem == null || selItem.IsImageFileExist == false)
            {
                LMessageBox.Show("　　请在左侧工作区管理器中选择一个“png”资源文件（该资源文件必须是由 Lunar Concept 生成的才行）。", Globals.AppName,
                           MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            //尝试从注册表中读取安装的hhw.exe的路径
            RegistryKey regSubKey;
            RegistryKey regKey = Registry.LocalMachine;
            string strRegPath = @"SOFTWARE\Lunar Concept";
            regSubKey = regKey.OpenSubKey(strRegPath);
            if (regSubKey == null)
            {
                LMessageBox.Show("未能在此计算机上找到 Lunar Concept，无法调用它来绘制概念图。", Globals.AppName,
                     MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }
            else
            {
                string lunarConceptInstalledPath = regSubKey.GetValue("InstalledPath").ToString();
                System.Diagnostics.Process.Start(lunarConceptInstalledPath, $"\"{selItem.FullPath}\"");
            }
        }

        /// <summary>
        /// 调用 Windows 系统中安装的 Lunar Mind 绘制概念图。
        /// </summary>
        private void miInsertMindMap_Click(object sender, RoutedEventArgs e)
        {
            //尝试从注册表中读取安装的hhw.exe的路径
            RegistryKey regSubKey;
            RegistryKey regKey = Registry.LocalMachine;
            string strRegPath = @"SOFTWARE\Lunar Mind";
            regSubKey = regKey.OpenSubKey(strRegPath);
            if (regSubKey == null)
            {
                LMessageBox.Show("未能在此计算机上找到 Lunar Mind，无法调用它来绘制思维导图。", Globals.AppName,
                     MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            string lunarConceptInstalledPath = regSubKey.GetValue("InstalledPath").ToString();

            if (File.Exists(lunarConceptInstalledPath) == false)
            {
                LMessageBox.Show("此计算机上未安装 Lunar Mind，无法调用它来绘制思维导图。", Globals.AppName,
                     MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            string imageResourceFolderPath;
            var selItem = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (selItem == null)
            {
                LMessageBox.Show("请在左侧工作区管理器中选择一个“Images~”资源文件夹作为添加概念图文件的位置。", Globals.AppName,
                         MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            WorkspaceTreeViewItem imageResourceItem = null;

            if (selItem.IsMarkdownFilePath)
            {
                var filePath = selItem.FullPath;
                imageResourceFolderPath = selItem.FullPath.Substring(0, selItem.FullPath.Length - 3) + "~\\Images~\\";
            }
            else if (selItem.IsImageResourceDirectory)
            {
                imageResourceFolderPath = selItem.FullPath;
                imageResourceItem = selItem;
            }
            else if (selItem.IsDirectoryExists)
            {
                var directoryInfo = new DirectoryInfo(selItem.FullPath);
                var metaFileImageResourceDirectoryPath =
                    (directoryInfo.FullName.EndsWith("\\") ? directoryInfo.FullName : (directoryInfo.FullName + "\\")) +
                    $"_{directoryInfo.Name}~\\Images~\\";
                imageResourceFolderPath = metaFileImageResourceDirectoryPath;
                foreach (var item in selItem.Items)
                {
                    var wtvisub = item as WorkspaceTreeViewItem;
                    if (wtvisub == null) continue;
                    if (wtvisub.FullPath == metaFileImageResourceDirectoryPath)
                    {
                        imageResourceItem = wtvisub;
                        break;
                    }
                }
            }
            else
            {
                LMessageBox.Show("请在左侧工作区管理器中选择一个“Images~”资源文件夹作为添加思维导图文件的位置。", Globals.AppName,
                           MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            try
            {
                var shortCommentName = InputBox.Show(Globals.AppName, "请输入思维导图短名（不能以“_”开头，尽量以字母开头）：", "思维导图", true, "不需要后缀名。", false);

                var shortName = ChinesePinYin.ToChinesePinYinText(shortCommentName);

                if (string.IsNullOrWhiteSpace(shortName) || shortName.StartsWith(".")) return;

                if (shortName.ToLower().EndsWith(".png") == false)
                {
                    shortName += ".png";
                }

                //刷新工作区管理器
                if (Directory.Exists(imageResourceFolderPath) == false)
                {
                    Directory.CreateDirectory(imageResourceFolderPath);

                    //须解决效率问题，不能直接调用此方法，也不能直接载入Xml。
                    //WorkspaceManager.RefreshWorkspaceTreeView(selItem.FullPath);

                    //基本思路：先添加“Images~”资源文件夹节点，再添加资源节点。
                    imageResourceItem = new WorkspaceTreeViewItem(
                        (Globals.MainWindow.ShowTitleInWorkspaceManager ? "图像~" : "Images~"),
                        imageResourceFolderPath, "[-]", "", "图像资源文件夹",
                        WorkspaceTreeViewItem.Type.ImageFolder, Globals.MainWindow);
                    selItem.Items.Add(imageResourceItem);
                }
                else
                {
                    imageResourceItem = FindWorkspaceTreeViewItem(imageResourceFolderPath);
                }

                var destFileName = imageResourceFolderPath + shortName;
                if (File.Exists(destFileName))
                {
                    LMessageBox.Show("　　已存在同名文件，操作无法完成。", Globals.AppName,
                               MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }

                File.Copy(Globals.InstalledPath + "MindMapExample.png", destFileName);

                if (imageResourceItem != null)
                {
                    var newImageItem = new WorkspaceTreeViewItem(destFileName, Globals.MainWindow);
                    imageResourceItem.Items.Add(newImageItem);
                    newImageItem.IsSelected = true;
                }

                var destFileInfo = new FileInfo(destFileName);
                var oldExtension = destFileInfo.Extension;

                var commentTextFilePath = destFileInfo.Directory.FullName + "\\" + "~.txt";

                if (shortCommentName.ToLower().EndsWith(".png") == false)
                {
                    shortCommentName += ".png";
                }

                if (File.Exists(commentTextFilePath))
                {
                    using (var stream = File.AppendText(commentTextFilePath))
                    {
                        stream.WriteLine($"{shortName}|{shortCommentName}");
                    }
                }
                else
                {
                    using (var stream = File.CreateText(commentTextFilePath))
                    {
                        stream.WriteLine($"{shortName}|{shortCommentName}");
                    }
                }

                System.Diagnostics.Process.Start(lunarConceptInstalledPath, $"\"{destFileName}\"");
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        /// <summary>
        /// 编辑由 Lunar Concept 生成的 png 格式的概念图。
        /// Lunar Concept 生成的 png 图形末尾被附加了一个纯文本的 XML 文件的所有内容，所以是可以编辑的。
        /// </summary>
        private void miEditMindMap_Click(object sender, RoutedEventArgs e)
        {
            var selItem = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (selItem == null || selItem.IsImageFileExist == false)
            {
                LMessageBox.Show("　　请在左侧工作区管理器中选择一个“png”资源文件（该资源文件必须是由 Lunar Map 生成的才行）。", Globals.AppName,
                           MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            //尝试从注册表中读取安装的LunarMind.exe的路径
            RegistryKey regSubKey;
            RegistryKey regKey = Registry.LocalMachine;
            string strRegPath = @"SOFTWARE\Lunar Mind";
            regSubKey = regKey.OpenSubKey(strRegPath);
            if (regSubKey == null)
            {
                LMessageBox.Show("未能在此计算机上找到 Lunar Mind，无法调用它来绘制思维导图。", Globals.AppName,
                     MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }
            else
            {
                string lunarMindInstalledPath = regSubKey.GetValue("InstalledPath").ToString();
                System.Diagnostics.Process.Start(lunarMindInstalledPath, $"\"{selItem.FullPath}\"");
            }
        }

        private void miShowSpaces_Click(object sender, RoutedEventArgs e)
        {
            miShowSpaces.IsChecked = !miShowSpaces.IsChecked;
            this.IsShowSpaces = miShowSpaces.IsChecked;
            App.ConfigManager.Set("IsShowSpaces", this.IsShowSpaces.ToString());
        }

        private void miShowEndOfLine_Click(object sender, RoutedEventArgs e)
        {
            miShowEndOfLine.IsChecked = !miShowEndOfLine.IsChecked;
            this.IsShowEndOfLine = miShowEndOfLine.IsChecked;
            App.ConfigManager.Set("IsShowEndOfLine", this.isShowEndOfLine.ToString());
        }

        private void miShowTabs_Click(object sender, RoutedEventArgs e)
        {
            miShowTabs.IsChecked = !miShowTabs.IsChecked;
            this.IsShowTabs = miShowTabs.IsChecked;
            App.ConfigManager.Set("IsShowTabs", this.IsShowTabs.ToString());
        }

        private void miShowLineNumbers_Click(object sender, RoutedEventArgs e)
        {
            miShowLineNumbers.IsChecked = !miShowLineNumbers.IsChecked;
            this.IsShowLineNumbers = miShowLineNumbers.IsChecked;
            App.ConfigManager.Set("IsShowLineNumbers", this.IsShowLineNumbers.ToString());
        }

        private void miShowTitleInWorkspaceManager_Click(object sender, RoutedEventArgs e)
        {
            SwitchShowTitleInWorkspaceManager();
        }

        private void ckxShowTitle_Click(object sender, RoutedEventArgs e)
        {
            SwitchShowTitleInWorkspaceManager();
        }

        private void SwitchShowTitleInWorkspaceManager()
        {
            ckxShowTitle.IsChecked =
            this.miShowTitleInWorkspaceManager.IsChecked =
            this.ShowTitleInWorkspaceManager = !this.ShowTitleInWorkspaceManager;
            App.ConfigManager.Set("ShowTitleInWorkspaceManager", this.ShowTitleInWorkspaceManager.ToString());
        }

        private void ckxShowTitle_Checked(object sender, RoutedEventArgs e)
        {
            ckxShowTitle.Foreground = Brushes.Blue;
            ckxShowTitle.FontWeight = FontWeights.Bold;
        }

        private void ckxShowTitle_Unchecked(object sender, RoutedEventArgs e)
        {
            ckxShowTitle.FontWeight = FontWeights.Normal;
            ckxShowTitle.Foreground = Brushes.Black;
        }

        private void miUnableDoubleInstance_Click(object sender, RoutedEventArgs e)
        {
            miUnableDoubleInstance.IsChecked =
                App.UnableDoubleInstance = !App.UnableDoubleInstance;
            App.ConfigManager.Set("UnableDoubleInstance", miUnableDoubleInstance.IsChecked.ToString());
        }

        private void btnMinizeToNoticeIcon_Click(object sender, RoutedEventArgs e)
        {
            windowState = this.WindowState;
            this.Hide();
        }

        private void miPreviewAsSlidesByRegion_Click(object sender, RoutedEventArgs e)
        {
            CompileAndPresentateHtml(CustomMarkdownSupport.PresentateHtmlSplitterType.Region);
        }

        private void miPreviewAsSlidesByTopLevelHeader_Click(object sender, RoutedEventArgs e)
        {
            CompileAndPresentateHtml(CustomMarkdownSupport.PresentateHtmlSplitterType.TopLevelHeader);
        }

        private void miPreviewAsSlides_Click(object sender, RoutedEventArgs e)
        {
            CompileAndPresentateHtml(CustomMarkdownSupport.PresentateHtmlSplitterType.HorizontalLine);
        }

        private void miPreviewAsSlidesByDocument_Click(object sender, RoutedEventArgs e)
        {
            CompileAndPresentateHtml(CustomMarkdownSupport.PresentateHtmlSplitterType.ByDocument);
        }

        private void miPreviewAsSlidesOnlyByHeaders_Click(object sender, RoutedEventArgs e)
        {
            CompileAndPresentateHtml(CustomMarkdownSupport.PresentateHtmlSplitterType.TopLevelHeader, true);
        }

        private void miPreviewWholeDocument_Click(object sender, RoutedEventArgs e)
        {
            CompileAndPresentateHtml(CustomMarkdownSupport.PresentateHtmlSplitterType.WholeDocument, true);
        }

        private void MetroWindow_Activated(object sender, EventArgs e)
        {
            var activedEditor = ActivedEditor;
            if (activedEditor != null)
            {
                activedEditor.EditorBase.Focus();
            }
        }

        private void miOpenChmFile_Click(object sender, RoutedEventArgs e)
        {
            var path = Globals.PathOfWorkspace;
            DirectoryInfo di = new DirectoryInfo(path);
            path = di.FullName + di.Name + ".chm";

            if (File.Exists(path) == false)
            {
                LMessageBox.Show("　　工作区目录下未找到编译好的 CHM 文件。请先尝试创建 CHM 工程并调用 Html Help Workshop 进行编译。",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            if (path.Contains(" "))
            {
                System.Diagnostics.Process.Start("explorer.exe", $"\"{path}\"");
            }
            else
            {
                System.Diagnostics.Process.Start("explorer.exe", $"{path}");
            }
        }

        private void miHighlightingAdvance_Click(object sender, RoutedEventArgs e)
        {
            this.HighlightingSetting = MarkDownEditorBase.HighlightingType.Advance;
            miHighlightingAdvance.IsChecked = true;
            miHighlightingOnlyHeaders.IsChecked = false;
            miHighlightingNone.IsChecked = false;
            App.ConfigManager.Set("HighlightingSetting", HighlightingSetting.ToString());
        }

        private void miHighlightingOnlyHeaders_Click(object sender, RoutedEventArgs e)
        {
            this.HighlightingSetting = MarkDownEditorBase.HighlightingType.OnlyHeaders;
            miHighlightingOnlyHeaders.IsChecked = true;
            miHighlightingNone.IsChecked = false;
            miHighlightingAdvance.IsChecked = false;
            App.ConfigManager.Set("HighlightingSetting", HighlightingSetting.ToString());
        }

        private void miHighlightingNone_Click(object sender, RoutedEventArgs e)
        {
            this.HighlightingSetting = MarkDownEditorBase.HighlightingType.None;
            miHighlightingOnlyHeaders.IsChecked = true;
            miHighlightingNone.IsChecked = false;
            miHighlightingAdvance.IsChecked = false;
            App.ConfigManager.Set("HighlightingSetting", HighlightingSetting.ToString());
        }

        private void miCompilePageMenu_Click(object sender, RoutedEventArgs e)
        {
            if (CompilePageMenu == false && HtmlHeadersCollapse == HtmlHeadersCollapseType.Auto)
            {
                var result = LMessageBox.Show("　　【自动折叠标题】选项会导致左边栏菜单中的链接不起作用（除非用户展开这些标题）。\r\n\r\n　　真的要继续吗？",
                    Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Warning);
                if (result != MessageBoxResult.Yes) return;
            }

            this.CompilePageMenu =
            miCompilePageMenu.IsChecked = !miCompilePageMenu.IsChecked;
            App.WorkspaceConfigManager.Set("CompilePageMenu", miCompilePageMenu.IsChecked.ToString());
        }

        private void miInsertNewColumn_Click(object sender, RoutedEventArgs e)
        {
            var selEditor = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (selEditor != null)
            {
                selEditor.EditorBase.InsertTableColumn();
                e.Handled = true;
            }
        }

        private void MetroWindow_Closed(object sender, EventArgs e)
        {
            var workspaceEditingFilePath = Globals.PathOfWorkspace + "\\~.editing";
            if (File.Exists(workspaceEditingFilePath))
            {
                File.Delete(workspaceEditingFilePath);
            }

            RememberWorkspaceHtmlCompileOptions();

            WorkspaceManager.SaveWorkspaceTreeviewToXml();

            App.ConfigManager.Set("PerspectiveMode", PerspectiveMode.ToString());
        }

        private void btnTopmost_Click(object sender, RoutedEventArgs e)
        {
            this.Topmost = !this.Topmost;
            if (this.Topmost)
            {
                backgroundGridTopMost.Background = Brushes.DarkCyan;
            }
            else
            {
                backgroundGridTopMost.Background = Brushes.Transparent;
            }
        }

        public void RefreshTextInfos()
        {
            var ae = ActivedEditor;
            if (ae == null)
            {
                tbSelCharsCount.Text =
                    tbSelLinesCount.Text =
                    tbCharsCount.Text =
                    tbLinesCount.Text = "-";
                return;
            }

            var sl = ae.EditorBase.SelectionLength;
            tbSelCharsCount.Text = (sl == 0 ? "-" : sl.ToString());

            var startLineNumber = ae.EditorBase.Document.GetLineByOffset(ae.EditorBase.SelectionStart).LineNumber;
            var endLineNumber = ae.EditorBase.Document.GetLineByOffset(ae.EditorBase.SelectionStart + ae.EditorBase.SelectionLength).LineNumber;
            tbSelLinesCount.Text = (endLineNumber - startLineNumber + 1).ToString();

            var cc = ae.EditorBase.Document.TextLength;
            tbCharsCount.Text = (cc == 0 ? "-" : cc.ToString());

            var lc = ae.EditorBase.Document.LineCount;
            tbLinesCount.Text = (lc == 0 ? "-" : lc.ToString());
        }

        private void btnTextInfos_Click(object sender, RoutedEventArgs e)
        {
            //文档总字符数
            //英文字符数
            //标点符号数
            //中文字符数
            //空白字符数
            //其它字符数

            var ae = ActivedEditor;
            if (ae == null)
            {
                LMessageBox.Show("当前未打开文档！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            var s = ae.EditorBase.Document.Text;

            int enLetterChars = 0;
            int chLetterChars = 0;
            int enNumbers = 0;
            int chNumbers = 0;
            int blankChars = 0;
            int chChars = 0;

            int elseChars = 0;

            foreach (char c in s)
            {
                if (c >= 'a' && c <= 'z') { enLetterChars++; continue; }
                if (c >= 'A' && c <= 'Z') { enLetterChars++; continue; }
                if (c >= 'ａ' && c <= 'ｚ') { chLetterChars++; continue; }
                if (c >= 'Ａ' && c <= 'Ｚ') { chLetterChars++; continue; }
                if (c >= '0' && c <= '9') { enNumbers++; continue; }
                if (c >= '０' && c <= '９') { chNumbers++; continue; }
                if (c == ' ' || c == '\t' || c == '　') { blankChars++; continue; }

                if (c >= 0x4e00 && c <= 0x9fbb) { chChars++; continue; }
            }

            elseChars = s.Length - enLetterChars - chLetterChars - enNumbers - chNumbers - blankChars - chChars;

            LMessageBox.Show($"文档总长：{s.Length}\r\n" +
                "-------------------\r\n" +
                $"半角英文：{enLetterChars}\r\n" +
                $"全角英文：{chLetterChars}\r\n" +
                "-------------------\r\n" +
                $"中文字符：{chChars}\r\n" +
                "-------------------\r\n" +
                $"半角数字：{enNumbers}\r\n" +
                $"全角数字：{chNumbers}\r\n" +
                "-------------------\r\n" +
                $"空白字符：{blankChars}\r\n" +
                $"其它字符：{elseChars}",
                Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Information);
        }

        private void bdAutoWrap_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            var edit = ActivedEditor;
            if (edit == null) return;

            edit.EditorBase.WordWrap = !edit.EditorBase.WordWrap;
            RefreshTextAutoWrapToStatusBar();
        }

        private void btnRefreshOutLine_Click(object sender, RoutedEventArgs e)
        {
            RefreshOutLines();
            if (tcManagerPanels.SelectedItem != tiOutLine)
                tcManagerPanels.SelectedItem = tiOutLine;
        }

        private void RefreshOutLines()
        {
            //string keyWord = @"^ {0,3}#{1,6}.*$";
            //FindHeaders(keyWord, tvOutLine);
            tvOutLine.Items.Clear();

            var efi = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (efi == null)
            {
                tvOutLine.Items.Add(new TreeViewItem() { Header = "...<未打开文档>..." });
                return;
            }

            Regex regex = new Regex(@"^ {0,3}[#＃]{1,6}.*$", RegexOptions.Multiline);
            var matches = regex.Matches(efi.EditorBase.Text);

            List<TreeViewItem> list = new List<TreeViewItem>();

            foreach (Match match in matches)
            {
                if (match.Success)
                {
                    var line = efi.EditorBase.Document.GetLineByOffset(match.Index);
                    var lineText = efi.EditorBase.Document.GetText(line.Offset, line.Length);
                    list.Add(new FindLineTreeViewItem(efi.FullFilePath, efi.ShortFileName, line.LineNumber,
                        match.Index - line.Offset, match.Length, lineText, null, FindLineTreeViewItem.ItemType.Header)
                    {
                        IsExpanded = true,
                        Tag = GetHeaderLevel(match.Value).ToString(),
                    });
                }
            }

            if (list.Count <= 0)
            {
                tvOutLine.Items.Add(new TreeViewItem() { Header = "...<此文档中无提纲>..." });
                return;
            }

            FindDocumentTreeViewItem fdi = new FindDocumentTreeViewItem(efi.FullFilePath, efi.ShortFileName)
            {
                IsExpanded = true,
                Tag = "0",
            };

            list.Insert(0, fdi);

            TreeViewItem parentItem = fdi;
            TreeViewItem previewItem = fdi;
            int parentItemLevel = 0;
            for (int i = 1; i < list.Count; i++)//第一个不算。
            {
                TreeViewItem ti = list[i];
                int tiLevel = int.Parse(ti.Tag as string);
                if (tiLevel == parentItemLevel + 1)
                {
                    parentItem.Items.Add(ti);
                }
                else if (tiLevel > parentItemLevel + 1)
                {
                    parentItem = previewItem;
                    parentItemLevel = parentItemLevel + 1;
                    parentItem.Items.Add(ti);
                }
                else if (tiLevel <= parentItemLevel)
                {
                    for (int j = list.IndexOf(ti) - 1; j >= 0; j--)
                    {
                        int liLevel = int.Parse(list[j].Tag as string);
                        if (liLevel < tiLevel)
                        {
                            parentItem = list[j];
                            parentItemLevel = liLevel;
                            break;
                        }
                    }

                    parentItem.Items.Add(ti);
                }
                previewItem = ti;
            }

            tvOutLine.Items.Add(fdi);
        }

        private int GetHeaderLevel(string headerText)
        {
            if (string.IsNullOrWhiteSpace(headerText)) return 0;
            Regex regex = new Regex(@"^ {0,3}[#＃]{1,6}.*$");
            var match = regex.Match(headerText);
            if (match.Success == false) return 0;

            int x = 0;
            foreach (char c in match.Value)
            {
                if (c == '#' || c == '＃')
                {
                    x++;
                }
            }
            if (x > 6) x = 6;//最大六级

            return x;
        }

        private void miPresentatorMode_Click(object sender, RoutedEventArgs e)
        {
            cmbPerspective.SelectedIndex = (int)Perspective.EditingAndPresentation;
        }

        private void btnMaxImagePreviewArea_Click(object sender, RoutedEventArgs e)
        {
            if (btnMaxImagePreviewArea.Tag.ToString() == "maxsized")
            {
                //恢复正常的演示模式比例
                gsResourcePreview.Visibility =
                  btnResetLeftToolbarLayout.Visibility = Visibility.Visible;

                rdLeftToolsTop.Height = new GridLength(1, GridUnitType.Star);
                cdLeftToolsArea.Width = new GridLength(360, GridUnitType.Pixel);
                tcResourcePreviewArea.Margin = new Thickness(0, 10, 0, 10);
                rdResourcePreviewArea.Height = new GridLength(360, GridUnitType.Pixel);

                btnMaxImagePreviewArea.Tag = "normal";
                btnMaxImagePreviewArea.LayoutTransform = null;
            }
            else
            {
                //最大化图像演示区域
                if (rdMainMenuArea.ActualHeight > 0)
                {
                    cmbPerspective.SelectedIndex = (int)Perspective.EditingAndPresentation;
                }

                gsResourcePreview.Visibility =
                    btnResetLeftToolbarLayout.Visibility = Visibility.Hidden;

                rdLeftToolsTop.Height = new GridLength(0);
                cdLeftToolsArea.Width = new GridLength(800, GridUnitType.Pixel);
                tcResourcePreviewArea.Margin = new Thickness(0, 0, 0, 10);
                rdResourcePreviewArea.Height = new GridLength(1, GridUnitType.Star);

                btnMaxImagePreviewArea.Tag = "maxsized";
                btnMaxImagePreviewArea.LayoutTransform = new RotateTransform()
                {
                    Angle = 180,
                    CenterX = 0.5,
                    CenterY = 0.5
                };
            }
        }

        private void btnFindAndCopyText_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                var keyWord = cmbFindText.Text;

                var efi = this.mainTabControl.SelectedItem as MarkdownEditor;
                if (efi != null)
                {
                    Regex regex = GetRegEx(keyWord, true, false);
                    var matches = regex.Matches(efi.EditorBase.Text);
                    StringBuilder sb = new StringBuilder();
                    foreach (Match match in matches)
                    {
                        if (match.Success)
                        {
                            var line = efi.EditorBase.Document.GetLineByOffset(match.Index);
                            var lineText = efi.EditorBase.Document.GetText(line.Offset, line.Length);
                            sb.Append(match.Value);
                            sb.Append("\r\n");
                        }
                    }
                    var result = sb.ToString();
                    if (string.IsNullOrEmpty(result))
                    {
                        LMessageBox.Show("未找到符合条件的文本。请检查搜索关键词或正则表达式。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                        return;
                    }
                    else
                    {
                        Clipboard.SetData(DataFormats.Text, sb.ToString());
                    }
                }
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        private void btnCollapseFindAndReplacePanel_Click(object sender, MouseButtonEventArgs e)
        {
            SwitchFindAndReplacePanelToggle();
        }

        public Perspective PerspectiveMode
        {
            get
            {
                if (cmbPerspective.SelectedItem == null) return Perspective.Normal;
                var selectedItem = cmbPerspective.SelectedItem as ComboBoxItem;
                if (selectedItem == null) return Perspective.Normal;
                Perspective i = (Perspective)Enum.Parse(typeof(Perspective), selectedItem.Tag.ToString());
                return i;
            }
        }

        private void cmbPerspective_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (cmbPerspective.SelectedItem == null) return;
            var selectedItem = cmbPerspective.SelectedItem as ComboBoxItem;
            if (selectedItem == null) return;
            Perspective perspective = (Perspective)Enum.Parse(typeof(Perspective), selectedItem.Tag.ToString());

            RefreshPersective(perspective);

            //记录下关闭程序前的透视图模式，下次启动时恢复。
            App.ConfigManager.Set("PerspectiveMode", PerspectiveMode.ToString());
        }

        private void RefreshPersective(Perspective perspective)
        {
            switch (perspective)
            {
                case Perspective.Normal:
                    {
                        this.MinWidth = 780; this.MinHeight = 560;
                        this.windowState = WindowState.Normal;

                        rdTopToolbarArea.Height =
                            rdMainMenuArea.Height = new GridLength(0, GridUnitType.Auto);
                        cdLeftToolsArea.Width = new GridLength(360, GridUnitType.Pixel);
                        cdRightToolsArea.Width =
                            cdMainEditArea.Width = new GridLength(3, GridUnitType.Star);
                        rdFindAndReplace.Height =
                            rdResourcePreviewArea.Height = new GridLength(0);

                        rdLeftToolsTop.Height = new GridLength(1, GridUnitType.Star);

                        tcManagerPanels.SelectedItem = tiWorkspace;//工作区管理器选项卡
                        tcRightToolBar.SelectedItem = tiHelp;//帮助文档选项卡
                        tcResourcePreviewArea.SelectedItem = tiFileNameRulePrompt;//命名规则说明选项卡

                        //tiHtmlSegmentPreview.Visibility = Visibility.Collapsed;
                        break;
                    }
                case Perspective.All:
                    {
                        this.MinWidth = 780; this.MinHeight = 560;
                        this.windowState = WindowState.Normal;

                        rdTopToolbarArea.Height =
                            rdMainMenuArea.Height = new GridLength(0, GridUnitType.Auto);
                        cdLeftToolsArea.Width = new GridLength(360, GridUnitType.Pixel);
                        cdRightToolsArea.Width =
                            cdMainEditArea.Width = new GridLength(3, GridUnitType.Star);
                        rdFindAndReplace.Height = new GridLength(0);

                        rdLeftToolsTop.Height = new GridLength(1, GridUnitType.Star);
                        rdResourcePreviewArea.Height = new GridLength(1, GridUnitType.Star);

                        tcManagerPanels.SelectedItem = tiWorkspace;//工作区管理器选项卡
                        tcRightToolBar.SelectedItem = tiHelp;//帮助文档选项卡
                        tcResourcePreviewArea.SelectedItem = tiFileNameRulePrompt;//命名规则说明选项卡

                        //tiHtmlSegmentPreview.Visibility = Visibility.Collapsed;
                        break;
                    }
                case Perspective.Editing:
                    {
                        this.MinWidth = 780; this.MinHeight = 560;
                        this.windowState = WindowState.Normal;

                        //与 Normal 相比，这种启动时自动折叠右工具栏。
                        rdTopToolbarArea.Height =
                            rdMainMenuArea.Height = new GridLength(0, GridUnitType.Auto);
                        cdLeftToolsArea.Width = new GridLength(2, GridUnitType.Star);
                        cdRightToolsArea.Width = new GridLength(0);
                        cdMainEditArea.Width = new GridLength(3, GridUnitType.Star);
                        rdFindAndReplace.Height =
                            rdResourcePreviewArea.Height = new GridLength(0);

                        rdLeftToolsTop.Height = new GridLength(1, GridUnitType.Star);

                        tcManagerPanels.SelectedItem = tiWorkspace;//工作区管理器选项卡
                        tcRightToolBar.SelectedItem = tiHelp;//帮助文档选项卡
                        tcResourcePreviewArea.SelectedItem = tiFileNameRulePrompt;//命名规则说明选项卡

                        //tiHtmlSegmentPreview.Visibility = Visibility.Collapsed;
                        break;
                    }
                case Perspective.FullScreenEditing:
                    {
                        this.MinWidth = 780; this.MinHeight = 560;
                        this.windowState = WindowState.Maximized;

                        rdTopToolbarArea.Height =
                            rdMainMenuArea.Height = new GridLength(0);
                        cdLeftToolsArea.Width = new GridLength(0, GridUnitType.Pixel);
                        cdRightToolsArea.Width = new GridLength(0, GridUnitType.Star);
                        cdMainEditArea.Width = new GridLength(3, GridUnitType.Star);
                        rdFindAndReplace.Height = new GridLength(0);
                        rdResourcePreviewArea.Height = new GridLength(0);

                        rdLeftToolsTop.Height = new GridLength(1, GridUnitType.Star);

                        //如果是临时切换，最好不动。
                        tcManagerPanels.SelectedItem = tiWorkspace;//工作区管理器选项卡
                        tcRightToolBar.SelectedItem = tiHelp;//帮助文档选项卡
                        tcResourcePreviewArea.SelectedItem = tiFileNameRulePrompt;//命名规则说明选项卡

                        //tiHtmlSegmentPreview.Visibility = Visibility.Collapsed;
                        break;
                    }
                case Perspective.EditingAndPreview:
                    {
                        this.MinWidth = 780; this.MinHeight = 560;
                        this.windowState = WindowState.Normal;

                        rdTopToolbarArea.Height =
                            rdMainMenuArea.Height = new GridLength(0);
                        cdLeftToolsArea.Width = new GridLength(0, GridUnitType.Pixel);
                        cdRightToolsArea.Width = new GridLength(3, GridUnitType.Star);
                        cdMainEditArea.Width = new GridLength(3, GridUnitType.Star);
                        rdFindAndReplace.Height = new GridLength(0);
                        rdResourcePreviewArea.Height = new GridLength(0);

                        tcRightToolBar.SelectedItem = tiHtmlPreview;//Html预览选项卡

                        rdLeftToolsTop.Height = new GridLength(1, GridUnitType.Star);

                        //如果是临时切换，最好不动。
                        //tcManagerPanels.SelectedItem = tiWorkspace;//工作区管理器选项卡
                        //tcResourcePreviewArea.SelectedItem = tiFileNameRulePrompt;//命名规则说明选项卡

                        //tiHtmlSegmentPreview.Visibility = Visibility.Collapsed;
                        break;
                    }
                case Perspective.FullScreenPreview:
                    {
                        this.MinWidth = 780; this.MinHeight = 560;
                        this.windowState = WindowState.Maximized;

                        rdTopToolbarArea.Height =
                            rdMainMenuArea.Height = new GridLength(0);
                        cdLeftToolsArea.Width = new GridLength(0, GridUnitType.Pixel);
                        cdRightToolsArea.Width = new GridLength(3, GridUnitType.Star);
                        cdMainEditArea.Width = new GridLength(0, GridUnitType.Star);
                        rdFindAndReplace.Height = new GridLength(0);

                        rdResourcePreviewArea.Height = new GridLength(0);
                        rdLeftToolsTop.Height = new GridLength(1, GridUnitType.Star);

                        tcRightToolBar.SelectedItem = tiHtmlPreview;//Html预览选项卡
                        //如果是临时切换，最好不动。
                        //tcManagerPanels.SelectedItem = tiWorkspace;//工作区管理器选项卡
                        //tcResourcePreviewArea.SelectedItem = tiFileNameRulePrompt;//命名规则说明选项卡

                        //tiHtmlSegmentPreview.Visibility = Visibility.Collapsed;
                        break;
                    }
                case Perspective.EditingAndPresentation:
                    {
                        this.MinWidth = 780; this.MinHeight = 560;
                        this.windowState = WindowState.Normal;

                        ShowPresentatorMode();
                        break;
                    }
                case Perspective.CompareMode:
                    {
                        this.MinWidth = 780; this.MinHeight = 560;
                        this.windowState = WindowState.Normal;

                        rdTopToolbarArea.Height =
                            rdMainMenuArea.Height = new GridLength(0);
                        cdLeftToolsArea.Width = new GridLength(0, GridUnitType.Pixel);
                        cdRightToolsArea.Width = new GridLength(3, GridUnitType.Star);
                        cdMainEditArea.Width = new GridLength(3, GridUnitType.Star);
                        rdFindAndReplace.Height = new GridLength(0);
                        rdResourcePreviewArea.Height = new GridLength(0);
                        rdLeftToolsTop.Height = new GridLength(1, GridUnitType.Star);
                        break;
                    }
                case Perspective.MiniMode:
                    {
                        //与全屏编辑模式不同，Mini模式适合用来摘抄文本，所以窗口更小且总在最前
                        this.MinWidth = 120; this.MinHeight = 90;
                        this.Width = 600; this.Height = 300;
                        this.windowState = WindowState.Normal;

                        if (this.Topmost == false)
                        {
                            this.Topmost = true;
                            backgroundGridTopMost.Background = Brushes.DarkCyan;
                        }

                        rdTopToolbarArea.Height =
                            rdMainMenuArea.Height = new GridLength(0);
                        cdLeftToolsArea.Width = new GridLength(0, GridUnitType.Pixel);
                        cdRightToolsArea.Width = new GridLength(0, GridUnitType.Star);
                        cdMainEditArea.Width = new GridLength(3, GridUnitType.Star);
                        rdFindAndReplace.Height = new GridLength(0);
                        rdResourcePreviewArea.Height = new GridLength(0);

                        rdLeftToolsTop.Height = new GridLength(1, GridUnitType.Star);

                        //如果是临时切换，最好不动。
                        tcManagerPanels.SelectedItem = tiWorkspace;//工作区管理器选项卡
                        tcRightToolBar.SelectedItem = tiHelp;//帮助文档选项卡
                        tcResourcePreviewArea.SelectedItem = tiFileNameRulePrompt;//命名规则说明选项卡

                        //tiHtmlSegmentPreview.Visibility = Visibility.Collapsed;
                        break;
                    }
            }
        }

        /// <summary>
        /// 开启“演讲者模式”。适用于“边写边讲”的情况：
        /// 左上角显示文档提纲（由六级标题组成，不自动同步）、左下角显示可能要预览的图像、右侧显示Markdown 文档编辑区。
        /// </summary>
        private void ShowPresentatorMode()
        {
            rdTopToolbarArea.Height =
                rdMainMenuArea.Height = new GridLength(0);
            cdLeftToolsArea.Width = new GridLength(
                Math.Min(this.Width / 2, Math.Max(360, ((this.WindowState == WindowState.Maximized) ? (SystemParameters.PrimaryScreenWidth / 2 - 20) : (this.Width / 2 - 20)))),
                GridUnitType.Pixel);
            cdRightToolsArea.Width = new GridLength(0, GridUnitType.Star);
            cdMainEditArea.Width = new GridLength(3, GridUnitType.Star);
            rdFindAndReplace.Height = new GridLength(0);

            rdLeftToolsTop.Height =
                rdResourcePreviewArea.Height = new GridLength(1, GridUnitType.Star);

            tcManagerPanels.SelectedItem = tiOutLine;//大纲选项卡
            tcResourcePreviewArea.SelectedItem = tiImagePreview;//图像文件预览选项卡

            //如果是临时切换，最好不动。
            //tcRightToolBar.SelectedItem = tiHelp;//帮助文档选项卡

            //tiHtmlSegmentPreview.Visibility = Visibility.Visible;

            tcManagerPanels.SelectedItem = tiOutLine;
            btnResetLeftToolbarLayout.Visibility = Visibility.Visible;

            tcRightToolBar.SelectedIndex = 1;
            var activeEditor = ActivedEditor;
            if (activeEditor != null)
            {
                activeEditor.EditorBase.UpdateLayout();
                cmbSearchArea2.SelectedIndex = 0;
                btnRefreshOutLine_Click(null, null);
            }

            //折叠所有单行的图像链接文本、二维文字表、自定义折叠区等。
            var efi = ActivedEditor;
            if (efi != null)
            {
                foreach (var i in efi.EditorBase.FoldingManager.AllFoldings)
                {
                    var folding = i.Tag as ICSharpCode.AvalonEdit.Folding.NewFolding;
                    if (folding != null && folding.IsSpecial)
                    {
                        i.IsFolded = true;
                    }
                }
            }

            //tiHtmlSegmentPreview.Visibility = Visibility.Visible;
        }

        private void miExitFullScreenC_Click(object sender, RoutedEventArgs e)
        {
            cmbPerspective.SelectedIndex = (int)Perspective.Normal;
        }

        private void tbSegmentPreviewHeader_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (e.ClickCount != 2) return;
            PreviewMarkdownSegment();
        }

        public void PreviewMarkdownSegment()
        {
            var editor = ActivedEditor;
            if (editor == null)
            {
                LMessageBox.Show("　　请先打开一个文档！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            var segmentText = "";

            var fstLine = editor.EditorBase.Document.GetLineByOffset(editor.EditorBase.SelectionStart);
            var lstLine = editor.EditorBase.Document.GetLineByOffset(editor.EditorBase.SelectionStart + editor.EditorBase.SelectionLength);

            segmentText = editor.EditorBase.Document.GetText(fstLine.Offset, lstLine.EndOffset - fstLine.Offset);

            if (string.IsNullOrWhiteSpace(segmentText))
            {
                LMessageBox.Show("　　没选中什么可以预览的内容！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            editor.CompileAndPresentateHtml(
                                 CustomMarkdownSupport.PresentateHtmlSplitterType.TextSegment,
                                 false, segmentText);
        }

        private void miCompileCleanHeaderLines_Click(object sender, RoutedEventArgs e)
        {
            miCompileCleanHeaderLines.IsChecked = !miCompileCleanHeaderLines.IsChecked;
            this.CleanHeaders = miCompileCleanHeaderLines.IsChecked;
            App.WorkspaceConfigManager.Set("CleanHeaders", miCompileCleanHeaderLines.IsChecked.ToString());
        }

        private void MetroWindow_Deactivated(object sender, EventArgs e)
        {
            foreach (var ue in this.mainTabControl.Items)
            {
                MarkdownEditor edit = ue as MarkdownEditor;
                if (edit == null) continue;

                edit.EditorBase.PopupToolBar.IsOpen = false;
            }
        }

        private void SwitchPopupToolbarEnabled_Click(object sender, RoutedEventArgs e)
        {
            this.IsPopupContextToolbarEnabled = !this.IsPopupContextToolbarEnabled;
            miIsPopupContextToolbarEnabled.IsChecked = this.IsPopupContextToolbarEnabled;
            App.ConfigManager.Set("IsPopupContextToolbarEnabled", miIsPopupContextToolbarEnabled.IsChecked.ToString());
        }

        private void segmentPreviewWebBrowser_LoadCompleted(object sender, System.Windows.Navigation.NavigationEventArgs e)
        {
            //设置片段预览区的高度。
            IHTMLDocument2 doc = segmentPreviewWebBrowser.Document as IHTMLDocument2;
            double zoom = mainTabControl.FontSize / 16;
            //doc.parentWindow.execScript($"document.body.style.zoom={zoom}");//初始缩放比例
            //doc.parentWindow.execScript($"document.body.style.fontSize='{mainTabControl.FontSize}px'");
            //使用zoom是可以实现缩放，但是页面过宽。
            //看起来还是应该放大字号

            //放在后面可以自动取出准确点的scrollHeight值。
            double height = (int)segmentPreviewWebBrowser.InvokeScript("getHeight");
            height *= zoom;
            height += 88;

            //if (segmentPreviewWebBrowser.ActualHeight > height) return;
            var gdl = new GridLength(
                Math.Max(0, Math.Min(Globals.MainWindow.leftToolBarGrid.ActualHeight, height)), GridUnitType.Pixel);
            //rdLeftToolsTop.MaxHeight = gdl.Value;//leftToolBarGrid.ActualHeight;
            rdLeftToolsTop.MaxHeight = leftToolBarGrid.ActualHeight - 58;
            rdLeftToolsTop.Height = gdl;
            rdResourcePreviewArea.Height = new GridLength(1, GridUnitType.Star);

            if (segmentPreviewWebBrowser.Visibility != Visibility.Visible) segmentPreviewWebBrowser.Visibility = Visibility.Visible;
        }

        private void btnSaveActiveDocument_Click(object sender, RoutedEventArgs e)
        {
            miSave_Click_1(sender, e);
        }

        private void btnPreviewSegmentHtml_Click(object sender, RoutedEventArgs e)
        {
            PreviewMarkdownSegment();
        }

        /// <summary>
        /// 向当前编辑位置粘贴一道或多道选择题。
        /// </summary>
        private void PasteOptionQuestion()
        {
            var editor = ActivedEditor;
            if (editor == null) return;

            try
            {
                var text = Clipboard.GetText(TextDataFormat.Text);
                if (string.IsNullOrWhiteSpace(text)) return;

                //尝试分解试题
                var textEditor = editor.EditorBase;
                var result = OptionQuestionProcesser.ConvertToExamText(text);
                if (string.IsNullOrWhiteSpace(result))
                {
                    return;//还是不加MessageBox了，太烦人了。
                }
                textEditor.BeginChange();
                textEditor.Document.Replace(textEditor.SelectionStart, textEditor.SelectionLength, result);
                textEditor.EndChange();
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message);
            }
        }

        private void miTryToPasteSelectExam_Click(object sender, RoutedEventArgs e)
        {
            PasteOptionQuestion();
        }

        private void btnPasetSelectExam_Click(object sender, RoutedEventArgs e)
        {
            PasteOptionQuestion();
        }

        private void miConvertLinesToCharCode_Click(object sender, RoutedEventArgs e)
        {
            var activeEditor = ActivedEditor;
            if (activeEditor == null)
            {
                LMessageBox.Show("请先打开一个文档并选中一些文本！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            var selText = activeEditor.EditorBase.SelectedText;
            if (string.IsNullOrEmpty(selText))
            {
                LMessageBox.Show("请在当前文档中先选中一些文本！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            StringBuilder sb = new StringBuilder();
            foreach (var c in selText)
            {
                var header = $" {c}";
                switch (c)
                {
                    case ' ': header = "\\s"; break;
                    case '　': header = "\\S"; break;
                    case '\r': header = "\\r"; break;
                    case '\n': header = "\\n"; break;
                    case '\t': header = "\\t"; break;
                }

                sb.Append($"     {header}，十进制码：{((int)c).ToString("D10")}，十六进制码：{CharToUnocode.CharacterToCoding(c)}\r\n");
            }

            activeEditor.EditorBase.BeginChange();
            activeEditor.EditorBase.SelectedText = $"\r\n{sb.ToString()}\r\n";
            activeEditor.EditorBase.EndChange();
        }

        private void btnMoveEntryToTopStart_Click(object sender, RoutedEventArgs e)
        {
            if (tvWorkDirectory.SelectedItem == null) return;
            var item = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (item == null) return;

            if (item.ItemType != WorkspaceTreeViewItem.Type.File &&
                item.ItemType != WorkspaceTreeViewItem.Type.Folder) return;

            var parentItem = item.ParentWorkspaceTreeViewItem;
            if (parentItem == null) return;
            if (parentItem.Items.Count <= 1) return;
            var index = parentItem.Items.IndexOf(item);
            if (index < 0 || index < 1) return;

            int topIndex = -1;
            for (int m = index - 1; m >= 0; m--)
            {
                WorkspaceTreeViewItem ti = parentItem.Items[m] as WorkspaceTreeViewItem;
                if (ti == null)
                {
                    topIndex = m + 1;
                    break;
                }

                if (ti.IsResourceDirectory)
                {
                    topIndex = m + 1;
                    break;
                }
            }

            if (topIndex < 0)
            {
                //如果没有资源文件夹，直接放到头部即可。
                parentItem.Items.RemoveAt(index);
                parentItem.Items.Insert(0, item);
                item.IsSelected = true;
                return;
            }

            parentItem.Items.RemoveAt(index);
            parentItem.Items.Insert(topIndex, item);

            item.IsSelected = true;
        }

        private void btnMoveEntryToPreview_Click(object sender, RoutedEventArgs e)
        {
            if (tvWorkDirectory.SelectedItem == null) return;
            var item = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (item == null) return;

            if (item.ItemType != WorkspaceTreeViewItem.Type.File &&
                item.ItemType != WorkspaceTreeViewItem.Type.Folder) return;

            var parentItem = item.ParentWorkspaceTreeViewItem;
            if (parentItem == null) return;
            if (parentItem.Items.Count <= 1) return;
            var index = parentItem.Items.IndexOf(item);
            if (index < 0 || index < 1) return;

            //如果前一个同级条目是资源文件目录，则直接返回。
            //资源文件夹条目总是在顶部的。
            WorkspaceTreeViewItem previewItem = null;
            if (index >= 1)
            {
                previewItem = parentItem.Items[index - 1] as WorkspaceTreeViewItem;
            }

            if (previewItem == null) return;
            if (previewItem.IsResourceDirectory) return;

            parentItem.Items.RemoveAt(index);
            parentItem.Items.Insert(index - 1, item);

            item.IsSelected = true;
        }

        private void btnMoveEntryToNext_Click(object sender, RoutedEventArgs e)
        {
            if (tvWorkDirectory.SelectedItem == null) return;
            var item = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (item == null) return;

            if (item.ItemType != WorkspaceTreeViewItem.Type.File &&
                item.ItemType != WorkspaceTreeViewItem.Type.Folder) return;

            var parentItem = item.ParentWorkspaceTreeViewItem;
            if (parentItem == null) return;
            if (parentItem.Items.Count <= 1) return;
            var index = parentItem.Items.IndexOf(item);
            if (index < 0 || index >= parentItem.Items.Count - 1) return;

            parentItem.Items.RemoveAt(index);
            parentItem.Items.Insert(index + 1, item);

            item.IsSelected = true;
        }

        private void btnMoveEntryToBottomEnd_Click(object sender, RoutedEventArgs e)
        {
            if (tvWorkDirectory.SelectedItem == null) return;
            var item = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (item == null) return;

            if (item.ItemType != WorkspaceTreeViewItem.Type.File &&
                item.ItemType != WorkspaceTreeViewItem.Type.Folder) return;

            var parentItem = item.ParentWorkspaceTreeViewItem;
            if (parentItem == null) return;
            if (parentItem.Items.Count <= 1) return;
            var index = parentItem.Items.IndexOf(item);
            if (index < 0 || index >= parentItem.Items.Count - 1) return;

            parentItem.Items.RemoveAt(index);
            parentItem.Items.Insert(parentItem.Items.Count, item);

            item.IsSelected = true;
        }

        private void btnMoveEntryToLeft_Click(object sender, RoutedEventArgs e)
        {
            if (tvWorkDirectory.SelectedItem == null) return;
            var item = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (item == null) return;

            if (item.ItemType != WorkspaceTreeViewItem.Type.File &&
                item.ItemType != WorkspaceTreeViewItem.Type.Folder) return;

            var parentItem = item.ParentWorkspaceTreeViewItem;
            if (parentItem == null) return;

            if (parentItem.ItemType != WorkspaceTreeViewItem.Type.Folder) return;

            if (parentItem == tvWorkDirectory.Items[0]) return;//根元素

            //左移是成为父元素的兄弟。

            var grandItem = parentItem.ParentWorkspaceTreeViewItem;
            if (grandItem == null) return;//根元素的直接下级不能再向左移动。
            var parentIndex = grandItem.Items.IndexOf(parentItem);
            if (parentIndex < 0) return;

            parentItem.Items.Remove(item);
            grandItem.Items.Insert(parentIndex + 1, item);

            item.IsSelected = true;
        }

        private void btnMoveEntryToRight_Click(object sender, RoutedEventArgs e)
        {
            if (tvWorkDirectory.SelectedItem == null) return;
            var item = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (item == null || item == tvWorkDirectory.Items[0]) return;     //根元素不能右移。
            var parentItem = item.ParentWorkspaceTreeViewItem;
            if (parentItem == null) return;

            //右移是指移动到前一个兄弟的下级。

            var index = parentItem.Items.IndexOf(item);
            if (index <= 0) return;//前面没有兄弟元素，无法右移。

            var previewItem = parentItem.Items[index - 1] as WorkspaceTreeViewItem;
            if (previewItem == null) return;

            if (previewItem.ItemType != WorkspaceTreeViewItem.Type.Folder) return;

            parentItem.Items.Remove(item);
            previewItem.Items.Add(item);

            item.IsSelected = true;
        }

        private void btnRebuildWorkspaceItemsFromDisk_Click(object sender, RoutedEventArgs e)
        {
            var result = LMessageBox.Show("　　此功能会清除当前工作区管理器的条目布局并重新读取整个工作区目录。\r\n\r\n　　此操作不可恢复且速度会很慢，真的要继续吗？。", Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Warning);
            if (result != MessageBoxResult.Yes) return;

            var activeEditor = ActivedEditor;


            workspaceManager.RefreshWorkspaceTreeView(activeEditor != null ? activeEditor.FullFilePath : null);
            workspaceManager.SaveWorkspaceTreeviewToXml();
        }

        private void btnCollapseMoveEntriesPanel_Click(object sender, RoutedEventArgs e)
        {
            //this.btnCollapseMoveEntriesPanel.IsChecked = !this.btnCollapseMoveEntriesPanel.IsChecked;

            if (this.btnCollapseMoveEntriesPanel.IsChecked == true)
            {
                bdMoveEntries.Visibility = Visibility.Collapsed;
                this.btnCollapseMoveEntriesPanel.Content = "◆";
            }
            else
            {
                bdMoveEntries.Visibility = Visibility.Visible;
                this.btnCollapseMoveEntriesPanel.Content = "◇";
            }
        }

        private ReplaceWithNumberWindow replaceNumberWindow = null;

        #region 换行时要保留的前导字符串
        private string leaderTextRegsA = "";
        /// <summary>
        /// 回车换行时应保留的前导字符。这应该是个正则表达式。
        /// A 类是原文本保留的。
        /// </summary>
        public string LeaderTextRegsA
        {
            get { return this.leaderTextRegsA; }
            set { this.leaderTextRegsA = value; }
        }

        private string leaderTextRegsB = "";
        /// <summary>
        /// 回车换行时应保留的前导字符。这应该是个正则表达式。
        /// B 类会将当前行原文本头部匹配的文本转换为半角空格添加到新行头部。
        /// </summary>
        public string LeaderTextRegsB
        {
            get { return this.leaderTextRegsB; }
            set { this.leaderTextRegsB = value; }
        }

        private string replaceLeaderChar = "$";
        /// <summary>
        /// 用于替换型前导字符串的头部第一个字符。
        /// </summary>
        public string ReplaceLeaderChar
        {
            get { return this.replaceLeaderChar; }
            set
            {
                if (string.IsNullOrWhiteSpace(value))
                {
                    this.replaceLeaderChar = "$";
                }
                else
                {
                    this.replaceLeaderChar = value.Substring(0, 1);//取第一个字符。
                }
            }
        }
        #endregion 换行时要保留的前导字符串

        private void miReplaceWithNumber_Click(object sender, RoutedEventArgs e)
        {
            if (replaceNumberWindow == null)
            {
                replaceNumberWindow = new ReplaceWithNumberWindow()
                {
                    Owner = this,
                    WindowStartupLocation = WindowStartupLocation.CenterOwner,
                };
            }

            replaceNumberWindow.Show();
        }

        private void btnSetVimKey_Click(object sender, RoutedEventArgs e)
        {
            var vimKeySelector = new VimKeySelector() { Owner = this, };
            switch (Globals.VimKey)
            {
                case Key.RightShift:
                    {
                        vimKeySelector.rbtnRightShift.IsChecked = true;
                        break;
                    }
                case Key.LeftCtrl:
                    {
                        vimKeySelector.rbtnLeftCtrl.IsChecked = true;
                        break;
                    }
                case Key.RightCtrl:
                    {
                        vimKeySelector.rbtnRightCtrl.IsChecked = true;
                        break;
                    }
                case Key.LeftShift:
                default:
                    {
                        vimKeySelector.rbtnLeftShift.IsChecked = true;
                        break;
                    }
            }

            if (vimKeySelector.ShowDialog() == true)
            {
                switch (vimKeySelector.VimKey)
                {
                    case Key.RightShift:
                        {
                            Globals.VimKey = Key.RightShift;
                            tbVimKeyText.Text = "RShift";
                            App.ConfigManager.Set("VimKey", "RightShift");
                            break;
                        }
                    case Key.LeftCtrl:
                        {
                            Globals.VimKey = Key.LeftCtrl;
                            tbVimKeyText.Text = "LCtrl";
                            App.ConfigManager.Set("VimKey", "LeftCtrl");
                            break;
                        }
                    case Key.RightCtrl:
                        {
                            Globals.VimKey = Key.RightCtrl;
                            tbVimKeyText.Text = "RCtrl";
                            App.ConfigManager.Set("VimKey", "RightCtrl");
                            break;
                        }
                    case Key.LeftShift:
                    default:
                        {
                            Globals.VimKey = Key.LeftShift;
                            tbVimKeyText.Text = "LShift";
                            App.ConfigManager.Set("VimKey", "LeftShift");
                            break;
                        }
                }
            }
        }

        private void miCopyFindedTaskList_Click(object sender, RoutedEventArgs e)
        {
            if (tvTaskList.Items.Count <= 0)
            {
                LMessageBox.Show("当前没有查找到任何任务列表！不能复制。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            var fdi = tvTaskList.Items[0] as FindDocumentTreeViewItem;
            if (fdi == null || fdi.Items.Count <= 0)
            {
                LMessageBox.Show("当前没有查找到任何任务列表！不能复制。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            var findedTaskListItemCount = 0;
            StringBuilder sb = new StringBuilder();

            sb.Append(fdi.HeaderTextBlock.Text);
            sb.Append("\r\n");

            foreach (var item in fdi.Items)
            {
                var ti = item as FindTaskListItem;
                if (ti == null) continue;

                findedTaskListItemCount++;

                sb.Append("　" + ti.LineText + "\r\n");

                foreach (var subItem in ti.Items)
                {
                    var tti = subItem as FindTaskListTimeTagItem;
                    if (tti == null) continue;

                    sb.Append("　　" + tti.LineText + "\r\n");
                }
            }

            if (findedTaskListItemCount <= 0)
            {
                LMessageBox.Show("当前没有查找到任何任务列表！不能复制。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }

            Clipboard.SetData(DataFormats.Text, sb.ToString());
        }

        private void miSetLineLeaderText_Click(object sender, RoutedEventArgs e)
        {
            var dlg = new LineLeaderTextSettingDialog() { Owner = this, WindowStartupLocation = WindowStartupLocation.CenterOwner, };
            dlg.ShowDialog();
        }

        private void miEnableBaseMDSyntax_Click(object sender, RoutedEventArgs e)
        {
            this.EnableBaseMDSyntax =
                                    miEnableBaseMDSyntax.IsChecked = !miEnableBaseMDSyntax.IsChecked;
            App.WorkspaceConfigManager.Set("EnableBaseMDSyntax", miEnableBaseMDSyntax.IsChecked.ToString());
        }

        private void tbnJoinLine_Click(object sender, RoutedEventArgs e)
        {
            TextCommandManager.JoinLines(null);
        }

        private void tbnInsertNewLine_Click(object sender, RoutedEventArgs e)
        {
            TextCommandManager.InsertNewLine(null);
        }

        #region 取控件内部某个子控件
        /// <summary>
        /// 此方法来自于：http://www.cnblogs.com/muzizongheng/p/3170850.html
        /// 作者：muzizongheng
        /// </summary>
        private static ChildItem FindVisualChildItem<ChildItem>(DependencyObject obj) where ChildItem : DependencyObject
        {
            if (null != obj)
            {
                for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
                {
                    DependencyObject child = VisualTreeHelper.GetChild(obj, i);
                    if (child != null && child is ChildItem)
                        return (ChildItem)child;
                    else
                    {
                        ChildItem childOfChild = FindVisualChildItem<ChildItem>(child);
                        if (childOfChild != null)
                            return childOfChild;
                    }
                }
            }
            return null;
        }

        /// <summary>
        /// 此方法来自于：http://www.cnblogs.com/muzizongheng/p/3170850.html
        /// 作者：muzizongheng
        /// </summary>
        private static ChildItem FindVisualChildItem<ChildItem>(DependencyObject obj, string name) where ChildItem : FrameworkElement
        {
            if (null != obj)
            {
                for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
                {
                    DependencyObject child = VisualTreeHelper.GetChild(obj, i);
                    if (child != null && child is ChildItem && (child as ChildItem).Name.Equals(name))
                    {
                        return (ChildItem)child;
                    }
                    else
                    {
                        ChildItem childOfChild = FindVisualChildItem<ChildItem>(child, name);
                        if (childOfChild != null && childOfChild is ChildItem && (childOfChild as ChildItem).Name.Equals(name))
                        {
                            return childOfChild;
                        }
                    }
                }
            }
            return null;
        }
        #endregion
    }

    public enum Perspective
    {
        Normal = 0, Default = 0,            // 普通模式
        All = 1,                            // 全部显示
        Editing = 2,                        // 编辑模式
        FullScreenEditing = 3,              // 全屏编辑
        EditingAndPreview = 4,              // 编辑预览
        FullScreenPreview = 5,              // 全屏预览
        EditingAndPresentation = 6,         // 边写边讲
        CompareMode = 7,                    // 对照模式
        MiniMode = 8,                       // 迷你模式
    }

    /// <summary>
    /// 按名称对工作区管理器中的同级条目进行排序。
    /// </summary>
    public class FileSystemEntriesCompare : IComparer<string>
    {
        public int Compare(string x, string y)
        {
            return x.ToLower().CompareTo(y.ToLower());
        }
    }
}